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 1c89dae3256..63dc76be271 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -6,7 +6,7 @@ from typing import Final 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 b51fa361121..ac9a9c83be2 100644 --- a/homeassistant/components/tuya/base.py +++ b/homeassistant/components/tuya/base.py @@ -74,7 +74,16 @@ class IntegerTypeData: @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 00000000000..4d9b769791c --- /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 91368044723..7cf25f13015 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 00000000000..319bc2602e1 --- /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 caa58ca58e2..a0501a41959 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 00000000000..14a2f350f88 --- /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 00000000000..7613c561759 --- /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 00000000000..63bf11b8c07 --- /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 004cb1a268f..a54eab486e6 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 cee03bf2677..9c9a715e5b1 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 00000000000..4bb3fc0fc09 --- /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 450d01ff8a5..ed19402786f 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 00000000000..f77e38ce6fa --- /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 e00e4c23097..60fd251d0a5 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 b7340913f78..f4a924d02db 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 00000000000..a82ed912c19 --- /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 04b238a916d..02c3aa97f46 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 e07f6bc25f0..742aaa9f842 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 00000000000..46631acc69a --- /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 bd47b20aa7d..b3b09105941 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 00000000000..7defcb9708e --- /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 c7a90c580ea..312ef7d430d 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 e0bd3a56842..ecc0d93e747 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 00000000000..5610f0c79fd --- /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 c68ccd9e006..f7ef9b48449 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 500891c40a6..fe7f44123e9 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 00000000000..35033cd1e59 --- /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 00000000000..6ab4acc7e59 --- /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 dad18be0f6a..c59076ec57d 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 31b7f1f8880..9c8157ce06d 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 b7b04c78863..5f8c44d358b 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 520424e434f..27378ab3284 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 00000000000..6c5446fe9f8 --- /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 00000000000..9d11ff838f4 --- /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 7cdf7f885e7..0e90341ef03 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 b01bd3d8e8e..edb649341c8 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 00000000000..9bae4cf9827 --- /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 4ae9a2d228a..5bb2846243a 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 d979ff4821c..40fbbf86bc6 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 bf11ee5dd00..64eaa4c7733 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 fca38446c23..301be92c3b1 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 00000000000..a9437c09ba4 --- /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 a1e23fa4145..43b3557af3a 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 00000000000..42fc274e0a8 --- /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 c5524f10790..f3bd4c2d4c0 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 44917a2ca18..215801cc7c5 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 a956bf5ce62..d2bb9582a60 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 db7a9e684f9..5b97afb3bcc 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 a72adf25c50..07647dc6df3 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 4cd04c64d7b..8cd04ad16c7 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 49878948880..e095a8bdb14 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 6d280a4c038..24949d0933a 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 7db05dfb8ee..235a66c8993 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 ef10b98c5df..dc14b0d4011 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 63c8a6ebc43..b53f8bd127d 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 9b31f1615f7..d17c5f3a43c 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 00000000000..e6c5379e30e --- /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 5f2110e4509..616ad96a867 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 24052b026bd..add7d39e39a 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 55a2f055db0..694cb41cf44 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 8c2031843f4..9ecd510ae99 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 00000000000..06ba7d17aca --- /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 f5e6f8763c9..55ab5b1d17d 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 00000000000..b239b4b143e --- /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 1fa59992bc3..2783621ab09 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 0c8bb89ba02..17e7fe938d0 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 68999eba2b7..d7b79b282eb 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 6b1d1dd4c53..93ef3f7dc31 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 b42268fa889..6585c0245e2 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 f35d5bad343..b0cadc8c8fd 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 00000000000..f341a6314ee --- /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 0c3b36ba0af..b758a17357f 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 00000000000..baf8fbcbba4 --- /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 eaec481025c..61c22e7e6ed 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 6778b3539a0..dd553f293be 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 cc6af948b5b..6716b5c8a37 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 19ee8d73aa4..66d47c2a376 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 93ba7c006f0..b8b8cf1250e 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 712015e6c91..e9417cdac37 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 2c6d11e073c..ad4e9f34610 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 0955b54fda8..6c1a41dac91 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 00000000000..b1be9557565 --- /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 fc476a776a4..6267038a6e0 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 cd7322fc9e6..39794940fef 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 0ce9aa09de5..a0e57d47083 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 637b937a568..b6ec5bd1111 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 760835c8ea8..91710293751 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 a9215ea6553..7cdb9b2170d 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 906abd1b2e5..e48ff5d9b56 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 00000000000..3f9ee36865e --- /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 6584b15b3ad..5959fce55c2 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 ec6732304bd..928ab40e1af 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 fdefe8992e8..0131b39c22b 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 3918614af2e..6f7efc7d8b3 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 00000000000..835523c59d7 --- /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 588841a7be8..83aba4ae98a 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 d69c70c4845..e5dc88173d4 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 00000000000..8c54ea9fefc --- /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 00000000000..d72413d8e86 --- /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 44f835c57d9..668a292e39b 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 db9aadc0b60..24b945708d3 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 00000000000..4ca6ad5c3de --- /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 00000000000..2b5744077e1 --- /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 00000000000..095042b54b0 --- /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 00000000000..75652397406 --- /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 1d2ee7844f9..d398d589513 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 f606163c1fa..67273a20482 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 00000000000..29d0c122538 --- /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 aaee76bbb8d..25ad61ac70f 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 ae269e11867..2c604126b3c 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 27719b662fd..38e69556faa 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 3e8b72613ce..b1a57f0ead2 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 53e41c8d7be..c1c00bc1df6 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 2f9c288aaac..a228f0c0506 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 3373a23e407..d351645cebd 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 40e3aad4dc4..cb5016cc230 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 628ea98dc7b..f1471f4f5b6 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 00000000000..5ba4512fb35 --- /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 42fe10e6a51..166b4dc48af 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 f20636f9d18..371f1ce022e 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 236614d0619..80013ccb554 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 870b7cdbe5c..bf75bfe8190 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 50d6c30777c..6448d4d9576 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 944cd176580..9f19fa47b7b 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 06aa27e5b37..4c2959bafa2 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 1217eb53d6e..0eba3a94310 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 722d1c5c34c..26fe409bfc2 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 579617127ac..21f2bddcc35 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 721823efbc7..a4e412f7d01 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 79e3178f46f..b3943953621 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 c5dee661aa2..221f35ebba6 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 1f047f44ab4..6d682ff545b 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 00000000000..1f842e20cf6 --- /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 4f29e856865..23a73311f71 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 a01a685bf0c..e7125511ec3 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 ed36968e615..6eccc55fa52 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 afacd480ce1..01463118ec0 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 5378ff71ef1..dcdb8bcd728 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 bc058d00b60..5ff28250076 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 725169c668a..8debd33481f 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 00000000000..46631acc69a --- /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 6d316999cb5..87c43a08326 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 56af531b9d5..9875ffa97c2 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 a9ede7713f3..9838417fb49 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 1d727ded5d9..9a899257add 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 f5b11bd9d4d..e4ad51b4d02 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 00000000000..d6778a65bc7 --- /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 00000000000..7f7ce67da39 --- /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 00000000000..45c80923777 --- /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 00000000000..e89f90a6119 --- /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 00000000000..e4fa82ddf46 --- /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 00000000000..c05b4b56bc4 --- /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 00000000000..a43a8312e99 --- /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 00000000000..54b4b67c5e7 --- /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 00000000000..7896b5796ed --- /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 fa7c08d30dc..808556d2a30 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 dfe2f3cef74..10f6abeb604 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 00000000000..9f795e36734 --- /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 18cd08b5507..d4cb349c546 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 3a0f66f1245..a6f5c496f22 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 b270bd0f2e9..a7a583f3f45 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 78f4ac3095d..ccbc6049794 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 62c15e0ca20..3f8a1b423b3 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 c6d7f0f1892..5a6d8eb2cca 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 cee10e5f9fe..c70dff73bea 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 c16dc8c09d2..e0657d5fddf 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 4f702344029..e0c55131f31 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 d3de5c90868..b8511d68338 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 08756130a79..8e51dd6b352 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 80b86ec495c..575a28b2dc0 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 ff9cd09fe09..52d7dcff5d1 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 22c5e00a8e7..c8c1056a81c 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 953b807c081..2bf509d5ca1 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 591618bfb60..8fe5f6316f8 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 ef684cbc92c..fa92c795f25 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 a0711b5b506..99ca69f9724 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 00000000000..e2bfaa9118c --- /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 00000000000..27cedabdefa --- /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 00000000000..ba35bb73082 --- /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 00000000000..448590b7c0f --- /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 00000000000..260ba6fd7a1 --- /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 00000000000..b5ee9cfd01f --- /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 00000000000..43dcd322834 --- /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 00000000000..d92df8c0341 --- /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 dbc2c9b5ed3..13b26b44b76 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 00000000000..5c7ba4adb9d --- /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 f7325c02cff..2a9fb59e1c1 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 ae15bb06d19..23e59bbc57f 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 aea858c5bfc..a795bd52359 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 c5c8d79c281..23994c6296f 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 00000000000..24c8a037676 --- /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 cdde7efe151..04dbe9245c1 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 56683e692c9..43f00462515 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 158bed0466d..e00049101f9 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 b834a9fcb9d..b007468e11c 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 5c26847aacc..183692bb465 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 f23daf5d053..ac324431ba6 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 b007468e11c..c1973e5989a 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 183692bb465..1e929340cdd 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 886b0fce2ce..f41ebd8b1ce 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 d4935196cc7..8a8e1155ab9 100644 --- a/script/hassfest/__main__.py +++ b/script/hassfest/__main__.py @@ -43,6 +43,11 @@ HASS_PLUGINS = [ 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 69810686cc1..7006c1e6032 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 d2bd437c2d9..06c1353ce73 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -90,7 +90,7 @@ NO_IMPLICIT_REEXPORT_MODULES: set[str] = { 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 5ac43d51316..2e19a345bc3 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -125,6 +125,8 @@ CONF_TEMP_MAX = "max_temp" 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 @@ class MqttClimate(MqttEntity, ClimateEntity): 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 0f4f9b209c6..3b2da69f94b 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 97e2aff7bff..ff88412a6ef 100644 --- a/homeassistant/components/ping/binary_sensor.py +++ b/homeassistant/components/ping/binary_sensor.py @@ -140,7 +140,7 @@ class PingBinarySensor(RestoreEntity, BinarySensorEntity): 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 a8546cb43dd..12f82016a1c 100644 --- a/homeassistant/components/shelly/entity.py +++ b/homeassistant/components/shelly/entity.py @@ -562,6 +562,11 @@ class ShellySleepingBlockAttributeEntity(ShellyBlockAttributeEntity, RestoreEnti 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 91aca485917..284ba42b3c1 100644 --- a/homeassistant/components/hassio/ingress.py +++ b/homeassistant/components/hassio/ingress.py @@ -154,7 +154,11 @@ class HassIOIngress(HomeAssistantView): 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 | 140 +++++------------ 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 | 108 ++++++++----- 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 | 27 +++- 13 files changed, 452 insertions(+), 251 deletions(-) create mode 100644 homeassistant/components/tradfri/coordinator.py diff --git a/.coveragerc b/.coveragerc index c301d4e30d3..78ef0996c53 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 952ee54a0d8..6dd3236ea97 100644 --- a/homeassistant/components/tradfri/__init__.py +++ b/homeassistant/components/tradfri/__init__.py @@ -7,6 +7,9 @@ from typing import Any 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.const import CONF_HOST, EVENT_HOMEASSISTANT_STOP 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 @@ from .const import ( 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 async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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 async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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 34ad7b792b9..af923538fb2 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 @@ def handle_error( 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 + + self._device_id = self._device.id + self._api = handle_error(api) + self._attr_name = self._device.name + + self._attr_unique_id = f"{self._gateway_id}-{self._device.id}" + + @abstractmethod + @callback + def _refresh(self) -> None: + """Refresh device 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)) + def _handle_coordinator_update(self) -> None: + """ + Handle updated data from the coordinator. - async def async_added_to_hass(self) -> None: - """Start thread when added to hass.""" - self._async_start_observe() - - @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() - - @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) + Tests fails without this method. + """ + self._refresh() + super()._handle_coordinator_update() @property def device_info(self) -> DeviceInfo: @@ -154,10 +89,7 @@ class TradfriBaseDevice(TradfriBaseClass): 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 487eb0dae13..c87d2097929 100644 --- a/homeassistant/components/tradfri/const.py +++ b/homeassistant/components/tradfri/const.py @@ -37,3 +37,9 @@ PLATFORMS = [ ] 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 00000000000..1395478b6e9 --- /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 554650f9005..d4c3063f35d 100644 --- a/homeassistant/components/tradfri/cover.py +++ b/homeassistant/components/tradfri/cover.py @@ -11,8 +11,16 @@ from homeassistant.config_entries import ConfigEntry 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 @@ class TradfriCover(TradfriBaseDevice, CoverEntity): 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 7b64e883c44..36e1c8b08ad 100644 --- a/homeassistant/components/tradfri/fan.py +++ b/homeassistant/components/tradfri/fan.py @@ -16,15 +16,17 @@ from homeassistant.config_entries import ConfigEntry 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 @@ class TradfriAirPurifierFan(TradfriBaseDevice, FanEntity): 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 ca078a37e81..9b6ad3e9f06 100644 --- a/homeassistant/components/tradfri/light.py +++ b/homeassistant/components/tradfri/light.py @@ -5,6 +5,7 @@ from collections.abc import Callable 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.components.light import ( 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 @@ from .const import ( 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] + coordinator_data = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] + api = coordinator_data[KEY_API] - entities: list[TradfriBaseClass] = [ - TradfriLight(dev, api, gateway_id) for dev in devices if dev.has_light_control + 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) + super().__init__(coordinator=group_coordinator) - self._attr_unique_id = f"group-{gateway_id}-{device.id}" - self._attr_should_poll = True - self._refresh(device, write_ha=False) + self._group: Group = self.coordinator.data - async def async_update(self) -> None: - """Fetch new state data for the group. - - 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 @@ class TradfriGroup(TradfriBaseClass, LightEntity): 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 @@ class TradfriLight(TradfriBaseDevice, LightEntity): 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 5da3179c507..3d042fa6417 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.const import PERCENTAGE 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, + ) - @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) + self._refresh() # Set initial state + + 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 f8950d24720..e0e2467ca4b 100644 --- a/homeassistant/components/tradfri/switch.py +++ b/homeassistant/components/tradfri/switch.py @@ -11,8 +11,9 @@ from homeassistant.config_entries import ConfigEntry 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 5e28bdcd55c..feeb60ab7c9 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 13b7e59e103..4aa99f5778a 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 7de2c4dcb37..1ed24d7b080 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 @@ -348,19 +349,26 @@ async def test_group_turn_on(hass, mock_gateway, mock_api_factory): await setup_integration(hass) # 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 - ) await hass.services.async_call( "light", "turn_on", - {"entity_id": "light.tradfri_group_1", "brightness": 100}, + {"entity_id": "light.tradfri_group_mock_gateway_id_0"}, 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_1", "brightness": 100}, + blocking=True, + ) + await hass.services.async_call( + "light", + "turn_on", + { + "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 8ed5c39882e..01da8b8af3c 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 c1973e5989a..1ad6372a6a5 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 1b3211c4765..17f87680c62 100644 --- a/homeassistant/components/homewizard/config_flow.py +++ b/homeassistant/components/homewizard/config_flow.py @@ -127,6 +127,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 c3798b35678..b34c6906cc1 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 d1b0c7c2130..096f1405168 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 b9df7a807e6..658ed7904f4 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 @@ class SubscriptionRegistry(pv.AbstractSubscriptionRegistry): """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 @@ class SubscriptionRegistry(pv.AbstractSubscriptionRegistry): 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 da4392ccc09..8eec3e2f038 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 eb408834bca..0cec354e1ab 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 @@ SIMPLE_SENSOR: dict[str, HomeKitSensorEntityDescription] = { ), 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 @@ SIMPLE_SENSOR: dict[str, HomeKitSensorEntityDescription] = { ), 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 @@ SIMPLE_SENSOR: dict[str, HomeKitSensorEntityDescription] = { ), 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 371ee360adc..2cbdf924319 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 e6cfa1eaa5e..0ba9b0bee25 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 78d3efb64bb..f93adc732ba 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 7c7688be4ee..ed940cb6376 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 a082683cf21..da69b7fe309 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 c50e23bac13..4c57d94b2b8 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 71abf8f865c..1dc8a0eddf8 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 d0dfa9bad4f..db9c534f708 100644 --- a/homeassistant/components/homekit_controller/const.py +++ b/homeassistant/components/homekit_controller/const.py @@ -56,6 +56,12 @@ CHARACTERISTIC_PLATFORMS = { 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 c2b3dc6d7b3..9c76adf52a9 100644 --- a/homeassistant/components/homekit_controller/number.py +++ b/homeassistant/components/homekit_controller/number.py @@ -41,6 +41,42 @@ NUMBER_ENTITIES: dict[str, NumberEntityDescription] = { 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 2d540f31850..b2b85c70d6e 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 @@ from homeassistant.components.climate.const import ( 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 7c712c81e1f..7dd5974589c 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 4d064d5d326..67abae262d5 100644 --- a/homeassistant/components/roku/media_player.py +++ b/homeassistant/components/roku/media_player.py @@ -408,13 +408,13 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity): 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 1ad6372a6a5..58aea66c491 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 1e929340cdd..b2785535014 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 d42e06aceb8..a039b313702 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 3d042fa6417..3f8fa96857d 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 1e9ae718285..63d4b6f84e5 100644 --- a/tests/components/tradfri/test_sensor.py +++ b/tests/components/tradfri/test_sensor.py @@ -5,16 +5,13 @@ from unittest.mock import MagicMock, Mock 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 db9c534f708..8c20afcd06f 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 @@ CHARACTERISTIC_PLATFORMS = { 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 @@ CHARACTERISTIC_PLATFORMS = { 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 00000000000..55c12c77820 --- /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 00000000000..83f83e56ec2 --- /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 b2b85c70d6e..83378650b97 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 00000000000..ea22bf68c54 --- /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 7759c2fe360..ff317e21396 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 0c8a1cd9aad..4b4b798a2d8 100644 --- a/homeassistant/util/dt.py +++ b/homeassistant/util/dt.py @@ -5,16 +5,11 @@ import bisect 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 54d4d1b1d19..c8ee1d91368 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 26ad28428fa..efcf61b85fc 100755 --- a/setup.py +++ b/setup.py @@ -38,7 +38,6 @@ REQUIRES = [ "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 8a845077dc9..56f49307662 100644 --- a/homeassistant/components/cloud/alexa_config.py +++ b/homeassistant/components/cloud/alexa_config.py @@ -192,10 +192,10 @@ class CloudAlexaConfig(alexa_config.AbstractConfig): 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 096268c8fed..18d769df07f 100644 --- a/homeassistant/components/device_tracker/config_entry.py +++ b/homeassistant/components/device_tracker/config_entry.py @@ -224,6 +224,7 @@ class TrackerEntity(BaseTrackerEntity): """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 21f7363695e..41fdd8c32d3 100644 --- a/homeassistant/components/zone/__init__.py +++ b/homeassistant/components/zone/__init__.py @@ -10,6 +10,7 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.const import ( ATTR_EDITABLE, + ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE, CONF_ICON, @@ -22,14 +23,7 @@ from homeassistant.const import ( 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 @@ class Zone(entity.Entity): @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 @@ class Zone(entity.Entity): 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 399afd480c7..54cb87aa772 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") + 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" - - -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") - 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" + 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 b656f92fec7..d468c3a653f 100644 --- a/homeassistant/components/launch_library/sensor.py +++ b/homeassistant/components/launch_library/sensor.py @@ -85,7 +85,7 @@ SENSOR_DESCRIPTIONS: tuple[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 98c9786f131..98a7eb4c07c 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 58aea66c491..295d659f816 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 b2785535014..3aa2af3c05a 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 37f2d7f0057..5c36a0c71c3 100644 --- a/homeassistant/components/spotify/__init__.py +++ b/homeassistant/components/spotify/__init__.py @@ -4,6 +4,7 @@ import aiohttp 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 118848ad6dd..9442ea8160b 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 295d659f816..dbfd56379f2 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 3aa2af3c05a..08085b62fe9 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 e1d7a3fc28c..91704a59b51 100644 --- a/tests/components/renault/const.py +++ b/tests/components/renault/const.py @@ -228,7 +228,7 @@ MOCK_VEHICLES = { }, "endpoints_available": [ True, # cockpit - False, # hvac-status + True, # hvac-status True, # location True, # battery-status True, # charge-mode @@ -237,6 +237,7 @@ MOCK_VEHICLES = { "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 @@ MOCK_VEHICLES = { 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 a29752188a2..1eef7aff083 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 ff317e21396..44486677384 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 dbfd56379f2..69f4a2de845 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 08085b62fe9..0100ee79044 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 5093745a0bf..79b01140875 100644 --- a/homeassistant/components/tuya/const.py +++ b/homeassistant/components/tuya/const.py @@ -87,6 +87,8 @@ PLATFORMS = [ 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 ce89000acd2..5154afb9ab5 100644 --- a/homeassistant/components/tuya/select.py +++ b/homeassistant/components/tuya/select.py @@ -257,6 +257,23 @@ SELECTS: dict[str, tuple[SelectEntityDescription, ...]] = { 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 2799d2b82fc..95ed463ee81 100644 --- a/homeassistant/components/tuya/sensor.py +++ b/homeassistant/components/tuya/sensor.py @@ -729,6 +729,16 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = { 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 48bf653979e..c1171f81fcb 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 4e36aec5ee5..7041b22ebe6 100644 --- a/homeassistant/components/tuya/switch.py +++ b/homeassistant/components/tuya/switch.py @@ -514,6 +514,22 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { 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 8994d0b2fbd..1567f22be44 100644 --- a/homeassistant/components/yale_smart_alarm/config_flow.py +++ b/homeassistant/components/yale_smart_alarm/config_flow.py @@ -161,7 +161,10 @@ class YaleOptionsFlowHandler(OptionsFlow): 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 @@ class YaleOptionsFlowHandler(OptionsFlow): 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 a26978f537a..0f231fa9303 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 .connection import HKDevice, valid_serial_number 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 cd5662d73c9..d1b133468d5 100644 --- a/tests/components/homekit_controller/test_init.py +++ b/tests/components/homekit_controller/test_init.py @@ -4,8 +4,11 @@ from unittest.mock import patch 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 aa0a5e55057..b4ed617f901 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 78ef0996c53..e7f99d9982e 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 6fa5a3ba728..34949b8c0b8 100644 --- a/homeassistant/components/knx/expose.py +++ b/homeassistant/components/knx/expose.py @@ -148,8 +148,6 @@ class KNXExposeSensor: 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 71a86f1e397..cd15d77629c 100644 --- a/tests/components/knx/conftest.py +++ b/tests/components/knx/conftest.py @@ -13,7 +13,7 @@ from xknx.telegram import Telegram, TelegramDirection 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 @@ class KNXTestKit: # 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 00000000000..697ee45ac07 --- /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 25ec0f92604..cbe174127c4 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 00000000000..350f7f9865c --- /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 9bdaac69386..4ad186fd8b4 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 63469b85c0c..294fca09be5 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 b6981eeb66a..b439c443e12 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 043477ed3f9..64012b14637 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 12b612a8c10..728fd2c3db7 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 ad1cb5087f1..9a24d984c32 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 d27bddfcd05..2de3c1a479b 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 2035b818b44..bf6a4aca128 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 8ffe23497ef..09cee8a724a 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 af11f5c1c63..cceb7dd033d 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 55768c4f567..ee8bfebadf9 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 4584a383136..6cbd589e17a 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 341317af7fd..2596a9c81a1 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 916336f37a4..eaecf88293d 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 9c3fd3ea1b0..b9b06cfd020 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 fa87d80871f..4283bcb83b1 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 51fa75f66b4..1ccd77230b6 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 c4ac5e79282..a0a79f1ae3a 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 4ffe6730453..accbab5f1d3 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 215801cc7c5..a859f6fd3df 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 8e1049d859e..e396e08cfaf 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 7215f0c458f..a6ac2e228e2 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 a84a69a3d0d..641b4ba9f4a 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 655cfa38418..a46042abe87 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 999949d5dd0..a7c9fc032f1 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 9c5448266e6..d1e2c743301 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 960dc53b5ea..941c3601bea 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 00000000000..1468d652d21 --- /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 c32926cf57b..4e855e3bc07 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 5a08a12d970..782ac2bf6fe 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 1aa1ff0a611..b2ef3c16b1e 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 8d8fefd6e75..360010b4d57 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 3b16abc78f9..d68bd74668c 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 00000000000..916b438c046 --- /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 00000000000..d6deb0e84c6 --- /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 3a23d735f76..54d0a83ffcf 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 216511c62f5..905dbbe420d 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 e4d2c8f1488..caa82dddd41 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 98fec09e773..dc10e256864 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 8cf34ace24e..8d5fc315beb 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 fe5b80f72f1..3f149896b64 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 4f0d8008d25..231945d5233 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 9c79f86d4f7..2daf1d17c80 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 5ca00363476..801ec910745 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 e73ad4da420..fb1a65ced8e 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 7eca10016ea..0982c627526 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 dd353c25117..90a27fa6e51 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 8c2dcdb53ed..34c5fbdbaad 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 3a9370b5486..4a755519aef 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 d9bcdd38dfd..0a3782dc2c3 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 118192b6516..34c251770cc 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 4f3469d1357..7e659cfe8ac 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 4cc080805ac..77576133365 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 2e79776f89a..00aa509c958 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 508a652ce9b..5f65978596e 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 4fc37801fa0..236cd0072a9 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 42762bfddce..b5b12c25c03 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 fe0b10996c9..201ecbf3779 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 daedfe647f9..8e60f589fc5 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 b69f7ada2a2..10bfdae7ae7 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 737b92c642a..46ca14c81ac 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 2ee171365a4..1d8a85aba4a 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 db992a56305..7774753a16e 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 09fdba77266..83e374cdf6c 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 2803182629a..09aae548c62 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 d9dd41dbb80..c31f1f8a14a 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 29e3ebe9790..d3e39738b02 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 9be7164e051..bc23114f672 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 fb4325581dd..f563646753e 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 7039662468e..8065c4fb471 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 9ff1977dc53..f0963321cab 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 26d7b728771..bb9a58d8127 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 a4e412f7d01..033f78316a9 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 00000000000..f301eb5bb8c --- /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 6895aeb247a..47e330100c9 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 00000000000..67a4de8faf8 --- /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 d8fe133bff4..de1c6594003 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 00000000000..8e82b60bce1 --- /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 a8ac5c92924..2ffe32a5fef 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 00000000000..a2e96925a5b --- /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 aaf69256d36..e392cd4e4ff 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 121ffb0c6bf..41f56c135aa 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 00000000000..805c15c252a --- /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 3859cbda430..3444f6c0fdd 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 3e94aaeb4c5..0d372da549f 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 e2c4ad68a8d..beace8108c7 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 e2051be22b6..1e16d41880e 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 7db0d6f01cf..5c1a058e514 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 ee7c75d8408..7a4e9b8a02b 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 e0c55131f31..3105feec5f7 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 1577872391e..23dbb00b491 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 90b677d331f..9c773ec34a0 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 8e51dd6b352..29c34000089 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 a2559065018..3b976246769 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 03137f3e028..09b02ba8cb3 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 b3ab03143d6..34ddcfcd7a6 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 8a39fe53b07..a9ee45002cf 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 00000000000..27344f27bc8 --- /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 73a3f244f96..97c04631aa5 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 3d06e7f1db0..a8d5ab0ea67 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 81c5c35e430..6e2b57ae823 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 8d255d492a7..ae72742d560 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 0776d7cb0e5..59a8b4cb52f 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 99ca69f9724..cfc6a2fc8b4 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 9e93a791a35..9b45080b8f1 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 00000000000..032e59b5197 --- /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 00000000000..75ff376daa5 --- /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 00000000000..183ba3306d2 --- /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 00000000000..72f6abfd12a --- /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 00000000000..5ca514829f5 --- /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 04b238a916d..a6b86e99d42 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 00000000000..6767d5d4d68 --- /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 23994c6296f..c2e966aee73 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 2a58de56c32..f4104fe78a5 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 efb7d3a3579..ce9c57f5889 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -223,7 +223,7 @@ SENSORS: Final = { 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 @@ SENSORS: Final = { 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 17575ebe375..256a0d7d155 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 ce2178288a0..872f2d0c7a8 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 f285902985c..4b226b4402c 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 efcf61b85fc..c6f3f1fb02f 100755 --- a/setup.py +++ b/setup.py @@ -31,34 +31,6 @@ PROJECT_URLS = { 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 @@ setup( 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 42115132420..09cfc02dcce 100644 --- a/homeassistant/components/homekit/type_humidifiers.py +++ b/homeassistant/components/homekit/type_humidifiers.py @@ -190,7 +190,7 @@ class HumidifierDehumidifier(HomeAccessory): ) 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 380571aba70..7fa9fe02956 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 c8b5e01e5db..623b7d0573a 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 ace5934adbd..1d6345506c1 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 @@ class NetatmoDataHandler: ) ) + 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 @@ class NetatmoDataHandler: 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 9d83aa02977..58b1a0d4f43 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 6c5f3eef00a..56f33b04432 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 defcd757d0a..7c600f6b442 100644 --- a/homeassistant/components/netatmo/sensor.py +++ b/homeassistant/components/netatmo/sensor.py @@ -386,7 +386,6 @@ async def async_setup_entry( 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 b1cee88ee2f..2eaf713e8ee 100644 --- a/tests/components/netatmo/test_camera.py +++ b/tests/components/netatmo/test_camera.py @@ -364,7 +364,7 @@ async def test_camera_reconnect_webhook(hass, config_entry): 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 test_setup_component_no_devices(hass, config_entry): 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 test_camera_image_raises_exception(hass, config_entry, requests_mock): 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 950a45f1e4a..ffa68d75011 100644 --- a/tests/components/netatmo/test_init.py +++ b/tests/components/netatmo/test_init.py @@ -112,7 +112,7 @@ async def test_setup_component_with_config(hass, config_entry): 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 d28df01becc..a0992e7ea2c 100644 --- a/tests/components/netatmo/test_light.py +++ b/tests/components/netatmo/test_light.py @@ -11,6 +11,8 @@ from homeassistant.const import ATTR_ENTITY_ID, CONF_WEBHOOK_ID 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 test_setup_component_no_devices(hass, config_entry): """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 test_setup_component_no_devices(hass, config_entry): ) 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 bb5b0e52186..bbce2c985ed 100644 --- a/homeassistant/components/overkiz/cover_entities/awning.py +++ b/homeassistant/components/overkiz/cover_entities/awning.py @@ -48,7 +48,8 @@ class Awning(OverkizGenericCover): 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 476ed23ae4d..60484620df1 100644 --- a/homeassistant/components/overkiz/cover_entities/generic_cover.py +++ b/homeassistant/components/overkiz/cover_entities/generic_cover.py @@ -51,9 +51,10 @@ class OverkizGenericCover(OverkizEntity, CoverEntity): 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 6075267b8e6..b640a184f58 100644 --- a/homeassistant/components/overkiz/light.py +++ b/homeassistant/components/overkiz/light.py @@ -79,8 +79,9 @@ class OverkizLight(OverkizEntity, LightEntity): @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 f96279d6d7f..e966e98c3f1 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -471,9 +471,16 @@ class CastDevice(MediaPlayerEntity): "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 | 41 ++++++++++-------- .../components/vicare/water_heater.py | 42 +++++++++++-------- 2 files changed, 49 insertions(+), 34 deletions(-) diff --git a/homeassistant/components/vicare/climate.py b/homeassistant/components/vicare/climate.py index a528fd63614..451ea70edab 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 = [] + api = hass.data[DOMAIN][config_entry.entry_id][VICARE_API] + circuits = await hass.async_add_executor_job(_get_circuits, api) - 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") + 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 5912287a953..0107ff8fe4c 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 256a0d7d155..87c7d9e9102 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 8a8e1155ab9..c6a9799a502 100644 --- a/script/hassfest/__main__.py +++ b/script/hassfest/__main__.py @@ -12,6 +12,7 @@ from . import ( dhcp, json, manifest, + metadata, mqtt, mypy_config, requirements, @@ -41,6 +42,7 @@ INTEGRATION_PLUGINS = [ 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 00000000000..ab5ba3f036d --- /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 5f1988f3c26..6044cdb277c 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 4b226b4402c..0625178d78f 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 c6f3f1fb02f..403ba9f0a33 100755 --- a/setup.py +++ b/setup.py @@ -31,11 +31,8 @@ PROJECT_URLS = { 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 @@ setup( 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 614cab3d90a..06a31a4e10a 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 b8ff5c91a3c..985c799110d 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 bf72cb34119..0953fc67b0a 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 05033bb3347..18ed447ec53 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 3f0efcd45aa..c1fcd510c23 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 30f6f88bd29..24dcb5928d8 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 59263037e65..808212014c7 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 42834b3c149..d70837bd088 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 00000000000..627d0df767d --- /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 00000000000..6b97107c353 --- /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 9c686362d98..478e608700c 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 69f4a2de845..3a3ae91fe2b 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 0100ee79044..83086ebb786 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 0625178d78f..274e1ac362a 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 403ba9f0a33..febaab62be0 100755 --- a/setup.py +++ b/setup.py @@ -4,38 +4,17 @@ from datetime import datetime as dt 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 430169ea97d..31fd02544ff 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 5ec43b37550..3398fdca926 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 a824488327b..a01b5de133a 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 aee171eb46e..0532fa5c82c 100644 --- a/tests/components/shelly/test_device_trigger.py +++ b/tests/components/shelly/test_device_trigger.py @@ -29,25 +29,48 @@ from tests.common import ( ) -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 37dfdc0e554..b86a435e758 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -14,6 +14,7 @@ from fritzconnection.core.exceptions import ( FritzActionError, FritzActionFailedError, FritzConnectionException, + FritzInternalError, FritzLookUpError, FritzSecurityError, FritzServiceError, @@ -523,6 +524,7 @@ class AvmWrapper(FritzBoxTools): 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 c0498395a10..fcde48528b3 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 b483e789f96..27e4ddea930 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 800f8b654bb..701694e40e9 100644 --- a/homeassistant/components/withings/__init__.py +++ b/homeassistant/components/withings/__init__.py @@ -9,7 +9,7 @@ import asyncio 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 8da67a0b77a..56f7f7cdf91 100644 --- a/homeassistant/components/withings/common.py +++ b/homeassistant/components/withings/common.py @@ -1111,3 +1111,46 @@ class WithingsLocalOAuth2Implementation(LocalOAuth2Implementation): """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 0ca40d28440..f8753739519 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 71eb350410b..b90a004ed0b 100644 --- a/tests/components/withings/common.py +++ b/tests/components/withings/common.py @@ -216,13 +216,15 @@ class ComponentFactory: 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 210d1f669e9..2643ac18c24 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 00000000000..a02ff2d8e47 --- /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 00000000000..bc164a9b138 --- /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 00000000000..ab13e0a64ee --- /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 00000000000..7148896e454 --- /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 00000000000..27b801a71ed --- /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 94ec3529836..759fea7c813 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 aiohttp 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 87c7d9e9102..952729caf09 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 f299177a08e..5f49889b6c6 100644 --- a/tests/helpers/test_entity_registry.py +++ b/tests/helpers/test_entity_registry.py @@ -9,6 +9,7 @@ from homeassistant.const import EVENT_HOMEASSISTANT_START, STATE_UNAVAILABLE 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 b60889358fd..0b6cfff6bae 100644 --- a/homeassistant/components/nest/diagnostics.py +++ b/homeassistant/components/nest/diagnostics.py @@ -4,6 +4,7 @@ from __future__ import annotations 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 b13dbf662b9..9b060d38fbe 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 @@ from unittest.mock import patch 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 09930c18501..b603019da81 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 c9353c31bab..07fcfe6cb74 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 fd391a81bad..cbb74585778 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 6313b177f47..c47a2831246 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 70b0eff18e3..73ab948ecb4 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 bd311dd3d35..506ddfcfce0 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 9e4f8384404..06e11f9ae8b 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 6390ccea39c..73de35987ec 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 e69980ad122..087d5c38820 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 311674359c7..6b74c771b03 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 7d740bbe731..c7ac3e14022 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 430e51c6e9e..56dd205de68 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 d4e7bc71937..583e23611ef 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 24585804b45..f3aba33ce80 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 8297081ae9d..3e15f62710d 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 5d6a221dbbe..ed803a3e6a1 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 b2cc5f6d32c..ed568aa8a46 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 a762d698545..0acb39801b5 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 cd045f25715..94203b46752 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 bfa41b3eeb1..b608d18bb7a 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 b54fc9d918c..b8befe292eb 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 6dc79513e55..a4fd72f5bdb 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 9441cdb86bc..6e83f747bb1 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 33cb84706ff..21f7e251269 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 0d6c1380c20..6f590d410fd 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 b47f84f2fe5..6c3978460e4 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 f50876e6629..735b4b0a431 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 926549f768d..49c7f3985e5 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 078ecaae0da..c43b976416d 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 688c7c9fb3d..3b290146a09 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 ac9352bae44..13a08685c68 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 73136a2ff29..2ea4e495a2b 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 b3af0413bf1..03d662b9721 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 4e0209cc337..f060bf8d8c6 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 dc29ff6fff5..6979eab4516 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 acae105b54d..91811189000 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 a28c852d8db..b0da88a8450 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 08545f4c5b0..c91c92922b4 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 7b4978b56c1..5ba5180b914 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 01da8b8af3c..63d292d54ac 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 660ba9f06f1..4b72a12aa26 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 f6323b49354..90c2c81e552 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 068da7d64f4..d42233ffa2d 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 1470c075b04..c1d67fa9e57 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 eb9dc54ecd2..39e48372167 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 975e7f1ac31..415cb900dc2 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 db537287b05..dc05ee9b929 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 466bf938cb5..54500f5c95a 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 9849c0d84ee..d3ab0022a70 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 fb7ce828324..fcec645127f 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 223ceba7685..de6581c3772 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 c1a3fbd59a7..085f2573a21 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 761328ba3de..41dcb9b2b0b 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 59e72341150..41580aa39d0 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 1dd04753293..0500a585619 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 52125b5a79c..59c589931f4 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 5de15056b08..6cf5e2bf406 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 e808da42728..446551ec3a1 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 aa52bee230d..d6ef4094b07 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 add067ab0cc..c57530a9bf8 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 a59023bb3f5..4f298b2b5e9 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 941faf1b598..b334ab36b36 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 f784b029a01..807f7a9e05f 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 0a8abfa6500..2cd9453f4b8 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 c8923f3d541..c18bd8b5de2 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 04bde4b4617..44645397c2d 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 39c0d37e2e3..d9c0481fff6 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 b90e7e845cf..c4bc116b4a6 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 05f8fe65fb3..b7058494e5c 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 c7c37c9bd0d..712f90a0f26 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 564aef45f84..7552c024d62 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 ccf48a9b8c3..ad8ee782592 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 4c997152b5a..8a283b40f5f 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 16e841b942f..c4db1d640de 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 9698679a6d6..3e437f9932f 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 5e782c70868..e5f8b004502 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 2ed89c0bf5b..ecc4e13e54e 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 18285ebec00..4ce465abc36 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 1a6e94003ca..63f09e3dfb3 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 77a84c70de8..aaf1af72db9 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 cb91446e476..693d6ab465c 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 fce775d4b7d..72277a820e4 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 b1762f7fea3..88eefb7f9c0 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 dfd61b1b9a8..e0edcd934e6 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 33fab430453..6a0453752e9 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 f88bfb83ddf..68011bb7bb2 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 06e90d31942..91f563107ed 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 c9a75b063f6..12b4d54b391 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 b1a4cd0b358..2316a884b73 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 1113699cdca..d167d6b4207 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 6c10e7ff299..da820ccb91f 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 25e07086efe..651d5eda1af 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 e1bdaeb3144..5948bb1f94e 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 ba20014fdcf..571e7708bc6 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 4f0b72a2be8..d003c693dd0 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 bb7dea841e4..4928d92447e 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 b83c4c4cca9..3e55f6359c6 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 ebb9e4b5f62..73b83c24cce 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 7e785af57c1..bf2bb9290fc 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 1921ae4f575..2af5c8bcb2f 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 aa056409786..add24a8fd41 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 d02c10682e1..907211dbae6 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 cfcd7fe8d68..dc7bfae3830 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 656dd5bc93c..b00eef2067f 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 c032c2620ce..a56a97f272e 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 87410d8b572..3e7fc508719 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 758721d5f71..786f54ad636 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 30fecb9c6de..28bfec14760 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 6468eea0a27..29c49b68df5 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 deefcaeb906..7afd3002fcc 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 bd2349798fd..1397285a6fe 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 94356f95eaf..6fb6bbce87a 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 b631467e5e3..3734339a34b 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 1fd2b1737ad..35af18a8c30 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 317ee21a9b0..1209dff7495 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 8539a69e560..5bf4651096c 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 c8d0e8a4d9d..5675e573bc1 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 c8cbc5ba11e..1eeb2241db5 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 9621a49157a..e9076e3d3da 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 85f4e1caf9b..a514606a322 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 6133a67bcf1..b60ea3a576c 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 c79d63dda16..c61b4b24a30 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 eba3626a950..93c962f2d6c 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 35cc1413bdf..51d5982a595 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 3fba13121f1..d6fc946ab79 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 5cc2d900229..4073cb273d8 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 0da186e7924..40c176c5f82 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 1b7d51c1716..3e7d31fcb1c 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 792486c7a87..8a0eb430403 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 b8ac5bce5fa..3932df60631 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 48a36a908c3..9319eb8dd0f 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 dfe4e8c1b96..d7109aa65ee 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 d7d366befd4..48b02cb9795 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 3957b257364..fe451db44b8 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 08c77f048a0..6fc29343d04 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 d89c5a74db1..e15a7c3b80a 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 bf5fd347888..09d8090f4fc 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 4fd54a7a3c9..8b4576f312a 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 46edd2bacfa..078ea0ed211 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 1ae50233b1a..d403291a081 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 a4250e33a60..e3c1455b454 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 6e4aca44ad6..3632b23123b 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 390e8efe7d5..fcb963f345d 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 83a9e7dbf6b..8c643555fe7 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 a22ec48da90..c9fe52b7e13 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 99a021de73a..8a494d193b7 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 ad442b0621a..1712cea1578 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 92ab636b87f..64ec4bca3a7 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 6226968b5d3..da89298c873 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 7505f5e2438..4cffabe87fc 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 966df3ed858..d3482738450 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 78e32a4d749..7ea598e266c 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 06af3defac3..e4c5a1e0029 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 3b341d90669..2d84604d53a 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 b89ca55ce3d..8e230dcab38 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 a5eb96e1376..2ee922442e6 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 00c05702db7..f626ee165be 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 331597225f0..c8ebdc415ec 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 d8d05969893..8506ad75e3f 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 36a86137e87..c006a627f2f 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 37ed8a5c6bb..06bf8c7c0c8 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 86db950ccc5..0fb4e9a9d33 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 d7ad10ca062..a27b5a6bc79 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 6f22689b9ca..c7f4fbeef53 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 868e62f07c3..4d1f1ecdff0 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 25290a5d431..a7e6a29bfe8 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 5abbc7b252a..9d3047e442d 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 069956bdc9a..310b66c0d37 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 3fb7f1d5987..f16299ae474 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 a644ff394e0..4ad8d08adf5 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 042bf930d0e..81c85a93056 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 7df8bb8d4f3..b5435201c23 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 525283359c9..29b0f89cd4b 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 bbb5e09c446..f9a3af20059 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 09f9cf81cd1..c2d8f98d40b 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 5ce509bfc3c..211e500cc7d 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 caa6c3bb33a..d337ce72f86 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 ecdafb22b56..fbddcb4c0e6 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 af68bbf2993..ae953e42715 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 66874f760ff..1a9bb05e140 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 b3a37cf9e57..7bc7d5a0e49 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 7de047114fa..b9cdd74baa8 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 854f3a2f195..ede1025a6db 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 1eea9fbfbf1..317f72dbae9 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 7af4624669b..ccfce906047 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 b848a344f1f..39bfa2c8e37 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 fa85a0283d8..87f2370aace 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 fb27d8b803f..d01995bd28b 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 4e4d1200e56..9f66c7e1cd7 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 7480257fcaa..bfbd919c051 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 75511aba4a1..0a79bff792a 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 330fea7de8b..357b5aeb160 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 6d1e002012c..c93cd2bc6dd 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 cdad0dd3f0c..05b0a4bf19a 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 5fd3eb6638f..ee69961d1b0 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 ac324431ba6..7eb75f54a55 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 c243c0d45c8..fb9a9ea5d63 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 b32ff6b4c8a..4bef77aee8a 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 b802eac13c8..9a0372a193e 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 cc351441cdd..c7084d4cab4 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 e2d9e5e501d..39103e3ea3e 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 7fb7f998643..db3144e83e8 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 254be7b6857..846bff5f8ce 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 94d57b37cae..17486271268 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 9e553abcd62..b278bd8d196 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 98c02d0166e..26dc9f65bc2 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 b28d76a71cc..a33e01153b7 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 d2f3fc2e0f3..f78489a2ea1 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 698da72c3f4..9d5bc7f9328 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 aba5abff67c..8f54c816649 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 6a470e1ddbd..30dd4c5af50 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 5668cd6cb3f..ba8eecc4ae9 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 dbd793c49b3..a365237561a 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 0e7227797d2..20ad912d40c 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 474d08c4b0c..7a23156759d 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 77852e6d982..07619623756 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 bbf02d1ec9e..efd4ff3d28b 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 2b0b0f1a4ee..cce95957ff6 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 ebef78f9e7f..3a5f4fb8daa 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 5b064551cf9..a8d90d87ac3 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 f46401d2a6b..04bd538322e 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 90d50bdda43..b438174c256 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 1116451e15f..102cd0ac2eb 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 00d76a9c1d0..1695c7d0d84 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 f0f403912a6..e8c3af23398 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 b566f3447f4..70f5e129950 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 8800b4ef4b8..5b353141215 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 9053bb7ddfc..b69ec09bbe7 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 a828789daea..c3b4f1f028a 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 a243d767d99..0f4bef2b38f 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 3d9aca1a0f9..503719c425b 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 79472359ab9..c8a71d426e7 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 9957e4602bd..1efdc685a24 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 4de42e3190a..8dfb37ad551 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 90e33a82452..7ba3e1971f4 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 4967a6e87ba..fdf170e2ede 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 a4c338aa632..983dc60414a 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 a7f4fffa4d6..8a029ae6339 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 94817890160..ab5848a1fb7 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 08797541eed..ff2411db35a 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 77217166052..8b783e40758 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 94794bf536d..ba7f2e3664c 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 9a3e8bd4827..b620153bba7 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 a8f89401148..7209ee00024 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 61c629655ce..84f7f4e28e1 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 514ee712710..d0e669783d7 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 5f23eef642b..2ce097fb8cc 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 1bd0a73b7ab..12638679f5a 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 b9a4f8e6ddb..5667d539902 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 edbf0147e14..30ef34b6b34 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 4b54468e092..9981b3a1109 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 7133871da42..cdae867ca85 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 6482db7ae60..f6ba16b1c5a 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 b41c7b06c74..f5fc8bc61cc 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 641bfca520e..870b52446ba 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 7dc7c602b98..70723fc3676 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 9bf4932a953..7ea878f074e 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 09e6066e573..7a3a2ced5f7 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 49f44634bcb..66d3c84452a 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 6f7ff77efb7..c554c775079 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 9cfc008921b..3e7ebf24b16 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 832592f3f1b..b9ffea7d3df 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 6b9981fee23..8640f126ae4 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 ade3b25f31c..29b260c2fa3 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 71a6abdfbdd..f0334b5af92 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 e9656b69eb8..8db827a8c35 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 4f247b3e937..8a886053361 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 751faec56c7..60ecb9da74a 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 e0e0b68bcf4..2263b583ddd 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 8061163943d..7c57744fd3b 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 6c40ef6bf03..4b1d89e59b3 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 aa18ead9b6e..005307b24e1 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 a4699853b01..35daf519769 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 b96769af932..5184bc8c105 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 e80e3a4eeec..97836e7f145 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 d6b90f13f8a..e899a794e07 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 c1823459745..655590005bf 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 7e8a00aee72..11946e6238d 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 2c537c7b35a..df2feab5146 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 e00a85a9823..595afd061cc 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 42edf00ad25..5009e25cb60 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 44d4d4ca582..6b84f735c12 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 42e1e074c8e..5addb869994 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 6cebb34bc63..463f921f03b 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 06079bf0b5c..902a03b6c83 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 18bfc3abc54..39e798f99bf 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 f78ca1e258c..dc91ede5461 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 4263d5288ff..d6938916c9a 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 e72eb0a6da7..455f3bab675 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 be34babeeae..740dbbb9ff4 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 792629f801c..23a2f07b970 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 9cdf30ad42b..b86e86e2b58 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 345cecc2eb6..ce00edfc108 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 ef77dc04580..9077fef50fd 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 a9d67e915fa..b56f4a091f0 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 d56977dc9df..35e9414a1e6 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 1bdcd7670e6..9f2a4c0013f 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 7e148be103b..e1685cd47c3 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 3f01c9091c7..be1ffd1f0b2 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 1b0c0b190e6..40365aa860c 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 b53d44ff188..8e8d982d216 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 1fc34f47000..76ab1d7cf5c 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 09514d01cb5..a65af141e15 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 7b5093eb86b..8185c300053 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 1c17ee0fd3c..0fab41e103e 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 6e229edb893..a97265ca244 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 6e46b0883d9..3a39a7870a3 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 c4ba720bc6a..93df24c8509 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 9e6d4353259..71f71cae993 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 c7d1ca4d0ed..8cbc29f52bd 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 24091ec65c8..581fe53424b 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 b84d36131e5..b5229f7a0fe 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 922c0e9d173..c377d29d2a0 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 a27ab3a48d9..a2c0aecb58d 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 f850b39a620..3c8aef9f673 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 87624a4e4ae..2bb9111b269 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 18f296e1c53..5006b88a407 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 671b1d2ca57..f40ad1d194c 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 54919088262..23fdad892d2 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 2dc46615f3a..b034745ee31 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 d77075a0c56..746d702b689 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 f0a8888214a..bf6f00d66ad 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 27325354553..df600e357aa 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 4502bd039f4..a35f7752562 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 3e688bdef6f..e497927180a 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 7481cabb655..c6e958d3a10 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 ab05ab111f0..b404762fbf3 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 b8995006169..94c040f3b75 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 329c9fa504d..eed2ec45dd7 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 d63961b5cfa..7cf70540372 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 705bb7ecb4b..2d61852689a 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 ec3da32a76d..255dc8c52ea 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 126fa407a37..53ab1e6af47 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 83c4ee72345..7c3e66c7127 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 b6d3eb51f7a..f703f991f6f 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 c45d7fb38e9..146a3397297 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 84a2addc3e1..57b31e03dc7 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 d8d5182816b..2d16786bd39 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 f53e0deecd7..c07f9b2a270 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 cd393002e1d..e4e8ceb53ee 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 4e31b99c172..e3d7b275de7 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 fa4bcc44cc6..7b9b402cb8d 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 e00049101f9..a75c7f99e4c 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 2fad5acc0ce..e6f04ad1171 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 6444aa17d7d..65efae00277 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 4171322400a..63007f88bbb 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 355f4c9058b..2f209667daf 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 d3b4f95a82e..2839f74a5cd 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 9e38e9d724e..f3278956911 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 b6c3e565dc0..1ce70f25ea5 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 36cc905eabf..ad91ce528cc 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 e7d1c4bd64a..cfdd62933c0 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 ffdd7d8f49d..35333f6ea01 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 71174f216a4..6c573b0c0d4 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 db6832b04b4..d38d2d8cffe 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 8ac5f387635..7aaea34ea60 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 aa2271f2dd4..349fba8c7a2 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 299209e9b97..ec393125d24 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 9242428ebf7..eea4b2b82fe 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 eff9d26103d..769db5898c2 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 7cea7118882..c2adebae594 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 99a5ff3a463..b74b2e2bf2a 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 ba5ba4cd0a8..f89db2346d9 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 f0465315cef..07121b3695b 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 86adfbcfe05..4723a2a6fb9 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 35a92dbb51b..0d609c87eb5 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 ccf2bf81384..96127f39bbd 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 4f4264d7688..ae66e72bfcb 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 1466537259b..67a7581e897 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 2001531a396..85910b0eb9a 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 8636bd6ed94..21200789fbc 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 e01cae08511..0eb4dc57d9d 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 39b4e45196b..880d32b5877 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 3024bfb310b..75691e5fc26 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 74e6d89d9f8..1498c695505 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 90c4b5a9ec0..0abb52a1666 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 21fc51fa9ee..da5d4763775 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 c8e9c29e4e7..0506e589d54 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 6e7a4f9cded..dafdd7c86bc 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 5becef7fff2..ef13ea4d8bf 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 50841f21f3a..3b022c1e43d 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 12c1f84aa37..1cf66c9d438 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 68d5fb50746..d8cda2f16c7 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 3550b56d352..e5ba3a05941 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 1c65ebebdcc..b183548222d 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 57c89e52ee8..4aa01428d27 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 478e608700c..12ee32d532f 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 581a954df30..1632c8ba9a3 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 34fbf45c529..5be37a358ed 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 e8af27cb3b6..f862ca73e6c 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 c02393e0f54..9b583739c88 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 a46acb46dc6..1d49293169e 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 f605b32528e..29b80fb00e9 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 3343e24b277..c441f37078f 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 c1dea03aa09..75163f3a92f 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 49cb077dc79..c61b4f0cf93 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 bb015a059b9..5057013bd50 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 bdc92209947..cbb8db87e32 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 11b1b3e3fdd..c3a0b43f7de 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 42169105930..87c29013544 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 e17270a62a0..6e7a9cbee53 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 82723f97924..0c97b08f680 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 8ad99c8a5c2..618110051b6 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 ade1a149590..81572fe9cb7 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 96eda381506..5247e196988 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 378d6442e31..fa19ef81c8c 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 dfc6ad62d90..a9f8f64da06 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 ce75e72f5de..694089b1396 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 d2dbb12ebc5..aea63a692a5 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 8642423fd8d..8a9b7c506b4 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 a65c4998554..0f02bd6b8f7 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 2489078ebd6..4a07713fa30 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 2e6f58028e0..091bdaa5736 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 2aa3df8d167..9f826a4c4b2 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 951d5237736..6d4ea286317 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 a1d672ba595..b1b203a8a61 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 05121c81ac7..f908ad16179 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 e150fbe5c57..385ab88428a 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 29c2b1e7fa4..e289e7a2e14 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 ea2e951d084..396bbb91a9c 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 1b3548296ee..cbe517dd986 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 4c3ee64779a..d3d9c7d1376 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 f48236c7f37..d7b301f9c23 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 39c1686d03e..4f2dadde270 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 a7faa60cdcd..cd220500751 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 b1e3b0597b5..9a050154969 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 c4e5a5b7711..3a8984af253 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 cd7d0f48eec..a76c7d16d74 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 c83b135cb8a..6b8815f9318 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 df750156d1d..513cb5ac3da 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 463a0aa1052..7aa19224020 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 6132cda2710..08299ca5ddb 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 0b0114328ac..8146dad908c 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 ed390278969..7e8b933fc17 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 1f0360e265a..61a94ffda30 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 9b5f567c420..b4cda33ee80 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 0d023a96ad5..bd755f38e7e 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 94c7391b649..74685b56373 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 0596d4073eb..6143853f188 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 14205b8652d..0c16e660aa9 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 192b55bec57..0d235982462 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 ba559ffb41d..19e737f51ca 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 40dbb7d569c..1b502481764 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 bf54f217f24..997fbbc5a70 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 1f952d04fc9..c94893f61fd 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 a9d6a4ebf76..19ea941cb52 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 fe365f85f2c..5b334f57c98 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 45f87b36ec1..6cbf8a76f4a 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 2e685a8625c..fc821426542 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 e8b44173fe9..a80cfb12876 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 60bc862406d..948ce8703a1 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 4e12fcd009c..3ea322a6c63 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 28ceb8e6c45..cca92d9bcec 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 757f2ef24ad..54bcab8e3fd 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 e7173df21d9..e8357caeb64 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 d25d0fc731e..4aec8dbee1a 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 ea07cc5d85a..5c9436433ed 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 99453f21d45..ddb3d4474a3 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 5355dd252f8..a3331b5c991 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 f81c2402846..5006b1e659d 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 366f770ca3b..05eeac20f17 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 a2070daedd7..f74c77ed3a9 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 792563d3db8..c74f5745bfc 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 697afd54106..7867df3dbee 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 5dcccadb681..fd17557abe1 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 d1dbb30f2fc..ca4ff88c986 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 e5f2fc056dc..0d035d969dc 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 9315bf308b7..0dfdd03e5e5 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 853324c9408..ecb3a9e6c41 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 dfed6d623f4..4b600abc930 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 609b7497744..a63ed8b7e7b 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 34356e74a56..7931cca70cc 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 56bfac01859..0752fbc7b78 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 5c9c06776b8..7b44d2cfa95 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 8db94bb9817..2bc2763e777 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 241b9a5cff9..8d49a24a3d9 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 5b3de2cf62b..366bbdc3479 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 94f8c8b5788..15de916201c 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 63eca334d7b..cb1f3a176a4 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 eb08be180c6..70ca1046b9d 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 851e93dc67d..eeba565d994 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 735e2f35bf4..4ce203b2499 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 b051ba65b3b..72c2c8eb300 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 d7d3c064ad7..47bb7ce9bd9 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 309dc6bdb51..ac049f00316 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 10a7dc35ddc..b4fbc78f241 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 4a272ea0364..331f191d029 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 6fd4b13dee0..56f4855d460 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 85cb7100a65..68fc0e2c309 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 c8a72447188..dfe177b05a8 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 631414ad344..f641bfd7a57 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 58594f17577..93f359b4f78 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 c19cc701afc..40bfbe1683c 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 b2ed060bffa..7e42611dedf 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 9442ea8160b..33b719f88c9 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 463c42c3a64..8f7ffc2766a 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 b14f7594d71..debc12ae4e0 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 8ba27cb450e..d7125518329 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 4aed69a05f3..e02a0ba6526 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 3e745dc2d4b..a64411e610f 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 68adda3edea..eee0f3d6a77 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 2da0a5254a4..736adcf0c35 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 2daa6e43873..6c66b906ff6 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 bcbb96c7034..db73d5b519b 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 13e6a7bb745..b95eb9e8cca 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 7dd5974589c..6d7989e93cb 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 ad5857aa630..0c14c0189a0 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 f4864571735..a3b22a3c2cc 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 3320f902168..83a8c025014 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 27421b20936..01f2e2703e8 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 d09c21779fe..f8db41b1a31 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 ea0bdbcb0f3..78ec56799a5 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 9e8f0a30e87..7f72a7ba77d 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 34e249ccfc3..ef20651843e 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 cf147df4fe6..d3a56ebdee6 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 b8b2ef6e46a..f010d340147 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 a12d149550b..4b9b7a2c8d0 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 0e7928fb23b..f8aea92b0a0 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 25dfe678800..08fb1388b38 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 79067e47c73..eaa0121f1dd 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 9123a68b716..9621e18bf17 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 6aacb3015e1..6c4a391698b 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 86f0974b6d1..90e69afed18 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 09313dab0dd..016ade188f4 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 8720dfac879..a9a63ccd9f4 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 b48a148034b..cfe04f9b1f7 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 d31feb5a8e4..db9a5c9c48a 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 361a58379d3..a7ec66d8b83 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 d8e607ec816..78f6e0609bc 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 7eba9eb4bda..9e2a9363eff 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 bf0142628b4..bb9d9ad7569 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 3812a5de072..c427a547790 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 c4a3e3775ae..c6c4db1143b 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 01fdb22395c..227f19d2481 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 3299e052227..0875609db1e 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 568f2b878ae..30e766b6ec4 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 fc475c2f48e..e3d27b6b4fc 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 bf4aed39cc6..49e6a14b715 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 c6a8e491217..094b04f6306 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 def1359b1ee..817bdaccd3c 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 0b5d0febbe7..e95760fc1e0 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 dc711df0e8d..26321d17aef 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 8e494af013a..30ea49a359c 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 c33babf4913..43b9e465f52 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 1e0f1dc5bad..62cfca125f6 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 dccfdbe285a..9f5fd18d531 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 da7ee08ff59..bfca03d754f 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 8b939d1d522..ce166179969 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 2605ffd2914..d54bb9e0ec6 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 f6d4404884d..ac734393197 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 a360bb7491a..324900a1d97 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 d667ab7ea37..308c11f91a1 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 6a1edaf41ae..f27ec29996e 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 f70cf59b9b9..2a65de9ed11 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 054aaca2d76..7974215de64 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 b67a05d5753..b4a043e2f13 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 7d9a963b26c..9bec5d4a72e 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 cfae1d98a5b..734e1a44dcf 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 4eedc28d378..d1030cb7868 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 6d736ac44e7..d98304ebf23 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 2e3249f4551..675a60e4096 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 19cd258ce6f..76df9e18606 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 e270e810122..6ff42bd4800 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 84b1e6b9445..e5c9520f96b 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 56e722174b4..02f21c69fea 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 5535da860f0..5d67ed6bf89 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 e8a905ca8bc..17ae6db0232 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 1bde431e9d7..88d77b775c5 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 1adbab49fb2..144938b1822 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 a376654ede4..26d56416e64 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 50de11d8209..4b1555fa3de 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 4d417aec1a2..97647d87106 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 b482556f287..5986cb18c75 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 07819b7b639..721b0e90402 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 2b8c2fb5477..15091ec04f7 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 9906a4025a5..088ddc8dd7b 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 b80fa0926cd..56cd6876e9f 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 09a128c9b72..7ada3ea2a37 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 402083aa25d..9dcf1fee6dc 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 ec3089dc4be..f36917f1a01 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 73aac879a00..fc5c8a598cc 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 44d972ca16a..e1a5d20a987 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 e487d8d63f0..d565b7aa690 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 8de4b4c24dc..7f658c4409e 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 5e4db0b6770..39c69e6052f 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 ca5e4f1da53..47f645d7148 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 e815b482330..2856fe94240 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 3f83c35ffa9..feb9657ef31 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 1fe64defe36..5f6dd4e61aa 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 cb42752d966..20473b66f2a 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 2b7af28a916..b08b2381211 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 20c8ba1dfed..ddda3caf2ff 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 6420e39538e..789ac76512c 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 13def08280a..8675099c530 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 7d7280ecc5f..a0400cb543b 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 1a4a90031ba..1fe5316c78a 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 59415d31c1e..617698b8b02 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 c8ed7ceefdc..9ebf83b4acd 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 042ccd93091..c4a263aca19 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 cd779e1657b..9d2897abf66 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 37b7ed311cb..4536e703ce9 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 c436557cea9..1efefb62109 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 b4d96f6f9b1..5ee3e114f1f 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 cd4ee5a51a1..31c19e4614f 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 cc79ed12e1e..e8a63c9c40a 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 50561042ea7..529b4bcfb97 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 62a667af5b1..a9ebcb546b5 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 d49ee6a1255..d3ad7fbe2e1 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 f8c4dff1545..6904f90a402 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 bd30231396f..a1f52517690 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 68edea99838..06c2d4e0c6b 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 048762903e1..d5eec8db552 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 5d8029ddcf5..bb0f9d32e3e 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 0443987a87b..b71bbe91563 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 26b7421ef44..771d6f6fd9d 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 8e86fa3d2f8..28c2d222f3a 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 aa9a8741390..d9f2052bd53 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 3ac2e7e4b25..f14ea25768b 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 cb87c1ea8a3..33081cb967d 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 2f5927442a2..90a19526c7c 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 8e332df8f62..39d4d808a15 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 1b30e0483f7..4ef1b579e17 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 4032b7e27d6..a9b4da9b2fd 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 09cd080b4d7..a00819638f3 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 83a0ba6fbe3..e530c67b930 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 63e87ebf876..aa60958591c 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 dc32b6bfac5..f6dc4ae2843 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 d3be51f91d2..3960d218423 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 1ea02f29ae2..5d1ef4cc0dc 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 1531f96c545..378435b9ec0 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 c18ccbb6106..63e20212005 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 77a8511a671..7f0df1b1f3f 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 b388703e6bd..a73c8390ad8 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 1ac82d0b84c..1950b00a079 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 36a1d47623e..da1d4de6c13 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 6490468dc03..4001856b703 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 1f5843e5e6c..8f4fabc529d 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 e6670b0e4f6..994fcde1b29 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 c991eecebb2..874563745cf 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 8f7d203c215..f81d112e825 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 1b8772a36df..24f9324fe5e 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 2a9a7915e76..d0b94efe289 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 f34dc5684c3..5c1415bc8fc 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 1317bd9a558..318ecb8304e 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 c78f5152f13..871cd27166d 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 706f2d7ab2c..17f1c8586c0 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 ffd42b8b0fe..4e80eef6021 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 af19bd68a06..83953b81d53 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 71e546879b0..0739138ecc7 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 e901d66acbf..b3ed7d2ef2f 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 46656e4cb3d..d0716dcec3a 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 22dec33917d..a4b7064e564 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 75b64806dff..fd5d68e577f 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 8d5d2c16fbb..e4994049452 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 a9e0f74462e..26e1f92ef9a 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 2ac975ada4a..a52c2948557 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 17241dba196..f52f751fc01 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 6ae41e340ab..0680848f70a 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 d38a5c056b8..9c1f4566dc3 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 a1ba3b6d370..fb880f567d1 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 507ee518454..99e43c6654f 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 4fb0bd29ac5..aed87e9239d 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 965e84435db..4f4a6a8b4a8 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 f52ba0fd99d..2543ef580a9 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 c72e25d42eb..4a5ea07dc82 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 6fef7bf5d57..d9f5b51e0ef 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 84cf9eac007..5a87ae29483 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 0bd04961ec7..c71be7ee4fc 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 470177997d0..fee8faeab86 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 5a4cd70f4c7..803076e44dc 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 761379f1308..2637cfaa746 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 98a7eb4c07c..3b3058058b6 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 568db1afdc0..e14dc58cf29 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 c3a48b30402..ba44a69478d 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 f686a6ac1fc..5b534f861cc 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 aa3721fe645..494cae37b57 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 11624da7f53..286e18b0b17 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 818df8c83d9..3785ed0ecc7 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 eac179efa8d..48caa75a824 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 0fbd4e2ebe4..449b9a33e34 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 6089c00be48..1a754351e7b 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 aeadf541345..2a4978b1cc1 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 48f812f447a..d4818d44626 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 82f60abbd64..8699df289d7 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 95f5b3c7d0a..7b65b5d0faa 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 cf70a808829..f225ac25ae7 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 85a32bce331..95c53624069 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 7991cbccbb4..1a08c5c4703 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 3a2a3025277..733693720a0 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 d0643ed51a9..d048a59d38c 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 9df10f32931..ce5c76c72f0 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 acfb9e2178a..8cbb0f6f502 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 58d0f9778d7..e28062c74c0 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 fec9fdb6c6a..972de72a9c9 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 6074b64d664..881ac34c93f 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 d1c867cd4e6..f9ec5321c62 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 749f7bbc67c..f597093382e 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 6140abf4f2a..cdf9fa5567b 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 fbf9cc925ba..bd1a0d2a1e1 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 94ebef9f241..f2dacccb7c3 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 e235d35237f..12958a93825 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 13444c6ad69..bcc3eef933e 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 da2b94f5382..239e8c28910 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 85fbbef7928..303480c2e7f 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 55df2587898..840f2cd677d 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 4cb5770bed7..cbc0e147f5b 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 6bc3846ea67..b2c9cd82f5a 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 437e9479ae1..7fc86f707b3 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 7d07d57fc28..a50ef69d57e 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 5320b8023e9..7820e17a978 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 17156ae3490..edae33b75aa 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 140b1cf3132..23299542736 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 f5713c51680..1e952452f46 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 39f8ebae4ae..8101fd6bf79 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 45cf866f51f..98f2ab1de21 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 dfaf6587d3b..eb43edc7fec 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 dfc1ffba538..98c5e277f17 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 c57e23507c9..d953675965f 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 92324f338b5..699e2e5b7a4 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 f56255c736d..d6cc9938eba 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 04ddd8df571..7217fd5940b 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 @@ class Integration: """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 54d4944cf70..55cfd44bae9 100644 --- a/script/hassfest/manifest.py +++ b/script/hassfest/manifest.py @@ -244,6 +244,7 @@ MANIFEST_SCHEMA = vol.Schema( 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 68946a9de01..8cc923840c2 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 262bf4ce67f..d509da91304 100644 --- a/homeassistant/components/samsungtv/bridge.py +++ b/homeassistant/components/samsungtv/bridge.py @@ -5,6 +5,7 @@ from abc import ABC, abstractmethod 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 @@ class SamsungTVWSBridge(SamsungTVBridge): 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 89c4d02c942..74016d4492c 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 52b000bd1af..69398645d18 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 4dc94eb7f15..00000000000 --- 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 00000000000..a6db38ee103 --- /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 00000000000..8e9af2dac05 --- /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 83fdb88aae4..8d30e37749f 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 00000000000..1ee479c51da --- /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 90e25c258f1..5f5ef326485 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 598c1a1c728..1d431ffd2fd 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 8046606a655..ab3b16da4e9 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 d617e4a6abe..b1bef5448b9 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 e7acdc0562e..598c747d87b 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 00000000000..732e52ee843 --- /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 23a6001d77c..69fdaea4d00 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 046a04b1dc4..e6dd34cb64d 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 00000000000..8e3dc496a29 --- /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 1f15d6d6eb7..ae27270b52f 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 054ec10754c..c21b90a495a 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 1b3f17fe700..9da5f3622b6 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 0e5163b96cd..9793131eb4c 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 82e2fb54adc..215801cc7c5 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 cd133547d61..33d580d03f8 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 d458e959d0a..96677b262eb 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 3be2d91f1f3..b4e012bc5f6 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 00000000000..421a29064f7 --- /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 0f9da18f7a8..851cac800e6 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 00000000000..1c859fa18e3 --- /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 00000000000..9b0aff97897 --- /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 00000000000..3087454c361 --- /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 00000000000..7a81bab7b6a --- /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 00000000000..a3c80cfbccf --- /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 00000000000..e279ef020b8 --- /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 00000000000..dd6b3fcb286 --- /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 00000000000..39fe8e65536 --- /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 00000000000..f0e1588b486 --- /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 89386e76717..060e7e90248 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 35ddc764aa0..6363e9de21d 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 47d4434ed6e..61f4cfd8fea 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 e574b74ef91..3ed1eaf6488 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 1084d980ea5..d7178f6d135 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 b06b2bfee45..1996fd23786 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 2783621ab09..a7fb32ce7a0 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 2f64056e985..21bfa86dcec 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 f997f39a54e..8ddb7c798c1 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 acc102240a3..13bd4650378 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 e93689f3bec..1cdb614a3cc 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 25c636ac1c7..334e938d41e 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 893e22f1471..b74774c3c21 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 71aea5cc63c..89e61d009e9 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 96ec6d5f20f..11fc2917061 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 b40c4316bba..17b45578ba3 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 b58deb171be..e428cc37213 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 73b20532035..e3a300af77d 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 e759f88a15a..9446d1e9e89 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 b852b1f6192..cf201a68523 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 5cdd0da50b5..fe686797d77 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 2129b81aa10..e824d89da07 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 f1abd934221..3bbdb67e6ad 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 00000000000..a0d7fb1cbfe --- /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 00000000000..dea99c02991 --- /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 e99b11a9e7e..a403bb921c3 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 890afaa099c..214ea9265ca 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 a25c0c35672..245b0dc2a0c 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 989bb38bcaf..99783fd4a63 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 09552be40d4..34eaa019be2 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 06299000122..1aed5099412 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 f7c3d821e66..5d3b29018dd 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 10d7ed2757b..a7663fff6d6 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 00000000000..69dfcc5d841 --- /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 00000000000..b1f864ad85b --- /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 203ba41ae00..3ade230c467 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 070d272aa43..ca341378714 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 d4cb349c546..b5c2d1f18f7 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 902b4ea5231..6b80f58bc97 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 573b73570c2..17912370a99 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 3105feec5f7..ce690131579 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 b8511d68338..17846d1760d 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 23dbb00b491..0aadad14443 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 9c773ec34a0..f8c23abd578 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 3b976246769..8d52bf30cca 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 f1df3a531e8..613ce8cedd2 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 34ddcfcd7a6..9f575f3f5f4 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 52d7dcff5d1..009b8f62245 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 a9ee45002cf..71ce79519ed 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 c3defd52d71..c1ef00b7b3c 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 512296463e4..409a9b08a72 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 be16b1d4802..06a77bc974e 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 4e99ac3e90a..3612367090e 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 00000000000..9b98369a859 --- /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 00000000000..e9aa45f91f2 --- /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 00000000000..f4f3c0b2460 --- /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 45c32845701..4f8fbbff6e9 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 44ee9452fef..81bc9f86bf6 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 fa8cd415378..ae926fd522f 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 78528a8898b..a7d70b00a21 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 d931bc69d70..35de8dfdcdc 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 621b11e4af5..f52d88f5401 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 c2e966aee73..47716969d47 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 86e54e111bd..da01b435335 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 00000000000..a5118ffd849 --- /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 490b550e705..780ffd02719 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 274e1ac362a..061e0bbc0cb 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 febaab62be0..69bf65dd8a4 --- 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 16a8a8ff26e..d1b43da9e27 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 53de4f392b8..42849d4498d 100644 --- a/homeassistant/components/tuya/fan.py +++ b/homeassistant/components/tuya/fan.py @@ -137,7 +137,7 @@ class TuyaFanEntity(TuyaEntity, FanEntity): [ { "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 a1fd1ce42fb..1462ec77b8f 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 00000000000..6f99ab483e6 --- /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 00000000000..3212794fc85 --- /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 edb03c51603..6505ee2bcaa 100644 --- a/tests/components/fritz/test_config_flow.py +++ b/tests/components/fritz/test_config_flow.py @@ -3,9 +3,7 @@ import dataclasses 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 @@ from homeassistant.components.fritz.const import ( 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 @@ from homeassistant.data_entry_flow import ( 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 378435b9ec0..9464305cd16 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 6c9e0d87499..8875fb15b5b 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -392,6 +392,11 @@ DHCP = [ "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 cbe517dd986..190b5c790cc 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 8875fb15b5b..277bc6169e9 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -201,6 +201,11 @@ DHCP = [ "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 0c14c0189a0..70053e8ee40 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 277bc6169e9..0c7538f7266 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -255,6 +255,11 @@ DHCP = [ "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 c4bc116b4a6..8282795a8e9 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 0c7538f7266..f05a7f73e50 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -46,6 +46,11 @@ DHCP = [ "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 7289f7d416e..4e700df24cb 100644 --- a/homeassistant/components/isy994/config_flow.py +++ b/homeassistant/components/isy994/config_flow.py @@ -158,6 +158,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 e9a4c5dc4fb..b16f5c0070d 100644 --- a/tests/components/isy994/test_config_flow.py +++ b/tests/components/isy994/test_config_flow.py @@ -16,7 +16,12 @@ from homeassistant.components.isy994.const import ( 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 | 28 +++++++++++++----------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/fritz/common.py b/homeassistant/components/fritz/common.py index b86a435e758..9d1c4543857 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -343,14 +343,15 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator): 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 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator): 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 - new_device = True + 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 e7f99d9982e..426114c6576 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 e5b6ed9798a..cb2573eedb8 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 51487bdfaf2..af44e621a7f 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 eab1294ad10..5272053a517 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 @@ from requests.exceptions import HTTPError 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.entity_platform import AddEntitiesCallback 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 00000000000..e1703b54acb --- /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 00000000000..3d240041b67 --- /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 740dbbb9ff4..bd1aa967f07 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 00000000000..cdbaecbeba5 --- /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 00000000000..13483418ffa --- /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 bf64bc4a51c..365aa903b09 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -159,6 +159,7 @@ FLOWS = [ "ipp", "iqvia", "islamic_prayer_times", + "iss", "isy994", "izone", "jellyfin", diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 83086ebb786..52b93948e43 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 00000000000..7fa75a42ccb --- /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 00000000000..a20a8729f55 --- /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 13d65cb21f4..ae4059728fe 100644 --- a/homeassistant/components/xiaomi_aqara/binary_sensor.py +++ b/homeassistant/components/xiaomi_aqara/binary_sensor.py @@ -9,6 +9,7 @@ from homeassistant.config_entries import ConfigEntry 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 @@ class XiaomiNatgasSensor(XiaomiBinarySensor): 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 @@ class XiaomiMotionSensor(XiaomiBinarySensor): 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 @@ class XiaomiMotionSensor(XiaomiBinarySensor): 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 @@ class XiaomiDoorSensor(XiaomiBinarySensor): 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 @@ class XiaomiWaterLeakSensor(XiaomiBinarySensor): 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 @@ class XiaomiSmokeSensor(XiaomiBinarySensor): 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 34949b8c0b8..0963ec3be43 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 homeassistant.helpers.typing import ConfigType, StateType from .const import KNX_ADDRESS from .schema import ExposeSchema +_LOGGER = logging.getLogger(__name__) + @callback def create_knx_exposure( @@ -101,7 +105,10 @@ class KNXExposeSensor: """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 @@ class KNXExposeSensor: 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 @@ class KNXExposeSensor: 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 cbe174127c4..e5030eef461 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 0739138ecc7..79c8453431d 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 faa3d3a22f7..9151c81543a 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 3a3ae91fe2b..6cb6cfe7e66 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 52b93948e43..44b8d284982 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 d3482738450..fc90591cae6 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 21d4002bcdb..00a10b713d2 100644 --- a/homeassistant/components/efergy/sensor.py +++ b/homeassistant/components/efergy/sensor.py @@ -3,8 +3,10 @@ from __future__ import annotations 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.config_entries import ConfigEntry 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 @@ class EfergySensor(EfergyEntity, SensorEntity): 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 @@ class EfergySensor(EfergyEntity, SensorEntity): 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 6cb6cfe7e66..157acea0f53 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 44b8d284982..07ce89ccd05 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 4c26e25e5f4..ddddc56f4e4 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 e966e98c3f1..6cca4cfa20b 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -4,7 +4,6 @@ from __future__ import annotations 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 @@ class CastDevice(MediaPlayerEntity): 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 @@ class CastDevice(MediaPlayerEntity): ) ) + # 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 @@ class CastDevice(MediaPlayerEntity): 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 @@ class CastDevice(MediaPlayerEntity): 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 @@ class CastDevice(MediaPlayerEntity): 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 @@ class CastDevice(MediaPlayerEntity): 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 d849dc052e4..6154dd14bd2 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 00000000000..702b838334f --- /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 69398645d18..c685c991e17 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 3f8fa96857d..693ebeead00 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 .const import CONF_GATEWAY_ID, COORDINATOR, COORDINATOR_LIST, DOMAIN, KEY_A 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 f4e871d79e1..25f30237d0f 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 4aa99f5778a..4db4ed4e585 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 pytradfri.device.air_purifier_control import AirPurifierControl 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 63d4b6f84e5..04f65344125 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 3d8e1aa27ba..9fcccc52386 100644 --- a/homeassistant/components/deconz/scene.py +++ b/homeassistant/components/deconz/scene.py @@ -7,7 +7,7 @@ from typing import Any 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 @@ async def async_setup_entry( | 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 @@ class DeconzScene(Scene): 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 7d351a333cf..17da9f1141a 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 189eb1e6eb7..e6f74cd0529 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 2120635be91..3bf4f7c6aeb 100644 --- a/homeassistant/components/webostv/config_flow.py +++ b/homeassistant/components/webostv/config_flow.py @@ -178,11 +178,14 @@ class OptionsFlowHandler(config_entries.OptionsFlow): 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 4c61f5d243d..dc83ae8dc5c 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 6725df44451..9a7136223b9 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 20c3b56bd60..0cca8117080 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 75111f9892d..f4ac44d3b60 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 11df415d4c9..fbf72991e92 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 00000000000..aa5c22db175 --- /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 00000000000..4bde04e1695 --- /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 5d291f4cadb..aa33b469b1b 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 00000000000..dcf9a44ea4a --- /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 00000000000..51e8d3fd84e --- /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 0077ceddd46..e4203d7c177 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 00000000000..abe96f8cccc --- /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 00000000000..acc62eaa7d8 --- /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 00000000000..88693aaab09 --- /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 00000000000..fb9b1f4c79e --- /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 9c9a715e5b1..58431a4d313 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 733411f2465..b9db19d0346 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 00000000000..8caf8d08cec --- /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 94dfbefb86a..d6ddde76595 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 9606a440aab..d5139fcb8b8 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 00000000000..03e813c18ff --- /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 466096416ae..9f6290dc492 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 d3ac36bf0e2..ce7a38d0867 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 bccf453eb0b..b5b2f9fc0c2 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 37b07361ad8..d446d618746 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 00000000000..5e0497e76c0 --- /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 8071efb001f..58279f9bff9 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 00000000000..67dcf497bc0 --- /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 00000000000..07f99344a4a --- /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 7aabf592ee3..d0997e495c5 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 5d9d5079110..2d6380e59be 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 7186be6216c..61185dc14f7 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 00000000000..7d9ce5c434d --- /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 00000000000..d81a4031129 --- /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 00000000000..c2b3b7e1568 --- /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 6cec4b5050d..ad86023f818 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 86b6d408baa..7c25606016e 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 bde76bff399..5f8926f1a51 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 510159829cb..859c87db3fb 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 00000000000..65502dcaa23 --- /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 64f713f5bd8..a963a7ab486 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 00000000000..ff6ede166a9 --- /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 294fca09be5..03a750a186d 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 d1bf94170bf..4ca2f04550e 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 972aed55cc4..3bc015b6b55 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 70d8b8620c4..5624d3765d3 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 00000000000..86cf9781d3a --- /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 a58a0045e46..2e596948dc2 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 00000000000..4916cbdfe49 --- /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 1a0fedff9d0..7bc600a3644 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 00000000000..0cafe568193 --- /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 e7ee63e6e5b..0306932f146 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 00000000000..6368184212a --- /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 3f8701092d1..8b09d9885c2 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 00000000000..1ab872ffb71 --- /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 00000000000..25ded1810fe --- /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 6c7dbea4daa..689ff1c42c5 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 8abd2dac5e5..369064ba6cb 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 1994465c410..cc538845603 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 6a395059625..db1fff5cd04 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 00000000000..687bfdf71f9 --- /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 00000000000..eb3814331b9 --- /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 345aa943039..b8f14483174 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 00000000000..8a763643004 --- /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 3ee29277296..675c2b74d28 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 00000000000..b0989763567 --- /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 312ef7d430d..cb7723d297b 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 00000000000..4c6c4266476 --- /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 931024c0e96..c09810d8a43 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 bb821341818..d69f8206c46 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 ab4a4904857..f20cd394948 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 00000000000..f39ce9b4c9a --- /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 5563dadabc7..4885aead70a 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 35033cd1e59..d2990459f4f 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 00000000000..64f05f01903 --- /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 11642b57627..1489556e10d 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 450fa7707d1..feda280cc3f 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 00000000000..a39a263484b --- /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 6ab4acc7e59..97a0f70ec26 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 4a9930dd95d..58d60891613 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 00000000000..d86aef5d51d --- /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 45aadbd1730..43954a1f032 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 6cbd589e17a..0b667026100 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 277606b855b..f317d16eb41 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 9d11ff838f4..fcbf8246205 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 00000000000..58ed851546d --- /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 00000000000..fca625597d4 --- /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 828f6a24e84..e34e7593c77 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 00000000000..95b4ef549a7 --- /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 00000000000..d783704c0a9 --- /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 00000000000..e29d809ebff --- /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 921319f55d0..35f7967ccac 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 b4604218206..21e769013ba 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 00000000000..55722d53aeb --- /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 00000000000..065c29ab9ab --- /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 02edb707618..10441872c51 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 932b4b8a72e..efdc82ab438 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 00000000000..b2cefe66206 --- /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 00000000000..ff6ede166a9 --- /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 b04554fd41e..864ae263dba 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 00000000000..9ab59f40649 --- /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 6000b968e5e..021b024c78f 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 00000000000..223bb36392f --- /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 00000000000..f99ab671b8e --- /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 00000000000..ec60fefab42 --- /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 cb050046d50..6a637c735f7 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 00000000000..159cd52c341 --- /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 00000000000..870003bde5d --- /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 00000000000..34892b7c476 --- /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 f5e9e2f8629..ef519660db8 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 00000000000..2d9c2ad919c --- /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 00000000000..fa50f0901aa --- /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 00000000000..d529509749c --- /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 3312cea3576..6a7db18da61 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 f23a27c2b73..2601b89b2a1 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 00000000000..8722382b01b --- /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 026edf7221c..93beddb92a8 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 033dc26c856..17c9f8afe60 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 00000000000..35950f57d3b --- /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 00000000000..82b12411670 --- /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 00000000000..ac82268cef4 --- /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 00000000000..aad75b3bed0 --- /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 c45178d6a72..a40bc1321a1 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 00000000000..8037db16e53 --- /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 00000000000..1e898e15ce0 --- /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 00000000000..71a36bb8359 --- /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 00000000000..9076a635244 --- /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 9685e93f927..7693e15a9ec 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 00000000000..c78531f645f --- /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 00000000000..7bfce93a571 --- /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 7e137fac759..1f22f20b3a2 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 00000000000..70f90c5b5f4 --- /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 00000000000..4b01a4755c3 --- /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 1e866fa8059..53de0312b73 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 d8b430df5f4..95ac686e86e 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 accbab5f1d3..6862ab5208e 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 ee705850b03..60cc25c59ce 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 98180e11248..6f79c486519 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 83add749e47..2228a8031ac 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 7f5535fd04b..1953aa13e2e 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 61936c6ff56..cf4ccf81af6 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 00000000000..81137fe0bdc --- /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 79dc7af8131..fd074fcc0f8 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 00000000000..14b1971e625 --- /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 00000000000..9365f5ac690 --- /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 fe07d42744e..8ef272f0670 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 d1e2c743301..7d77525cc8a 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 00000000000..d5efbb90261 --- /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 90c98e491df..cfd20d603cb 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 00000000000..e956f89d381 --- /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 00000000000..14615cff002 --- /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 00000000000..f8ca0b40187 --- /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 3f8fd23b07c..bcf50b5d3da 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 7fe3f58cad6..7686665e6f6 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 328264d3adf..767cd5d5fb3 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 fe3e8fb7b43..8a452d98d03 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 00000000000..d529509749c --- /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 e08a2c5f6df..49eae73f25e 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 00000000000..ef6f9993b11 --- /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 00000000000..93beddb92a8 --- /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 ff8e13aed1f..ea479059ebb 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 00000000000..85e6cfdec61 --- /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 0faa3110287..da55be65f04 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 1ef802a9202..1c2e3094ec5 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 55f4b71f7b2..47ee5b8630a 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 00000000000..e807b3eacf7 --- /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 c19678ad0c4..101cdd83c48 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 00000000000..2916d86b242 --- /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 00000000000..7922f363bda --- /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 c69337fa2bb..821b2f6e72b 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 9a7e8094b11..a2cc864aeff 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 00000000000..66c671f99a3 --- /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 00000000000..2783abe00e6 --- /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 f7dc708a2d6..b98d170336f 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 916b438c046..f974693fd2a 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 00000000000..f10ded2b0b3 --- /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 c9e64b96906..dab904f0160 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 00000000000..7406eb095f2 --- /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 00000000000..1e898e15ce0 --- /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 932b4b8a72e..bdf8cc5c5bd 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 b6c25038bdb..13591ed9485 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 7005c83bf69..fe7c4d91253 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 c78da4db0c0..32dec17701c 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 3f149896b64..e6c5392a9c6 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 f888c15874b..c2b366b4d0f 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 dd69e0ec7c4..cf090f974f7 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 00000000000..ff6ede166a9 --- /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 fffbfae2249..369064ba6cb 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 aceb4ea78d5..649ab1f56e6 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 00000000000..79d60b8a2f7 --- /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 704cc017a9b..fd3619849c2 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 d8ceb8fe934..a366280ec35 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 00000000000..9ab59f40649 --- /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 ea7a2c4f9b2..a77fa8136bb 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 00000000000..218bebc5a98 --- /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 00000000000..7e1a9be8e79 --- /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 00000000000..4049e41ea24 --- /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 00000000000..09104143492 --- /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 00000000000..23841f83259 --- /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 00000000000..34a9644e9d0 --- /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 00000000000..ffd5861f9cf --- /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 00000000000..07f374b8a17 --- /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 00000000000..e59aa3a3be5 --- /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 2bc8cd2ef5a..d377b19f586 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 00000000000..ae7a7293429 --- /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 363e62a1b5f..7a35966f685 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 00000000000..2fda26fe566 --- /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 3f24589c235..886d6e3676e 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 281a9dc8931..806cea1df9b 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 00000000000..937edfdd914 --- /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 00000000000..93beddb92a8 --- /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 66d47c2a376..28e9033bc11 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 00000000000..a5343784fab --- /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 39794940fef..27b167c6551 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 00000000000..321f7f6ffef --- /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 b31bd6feb8a..24ba6ade2c0 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 00000000000..b829ba6e92b --- /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 00000000000..9ce2cf2399e --- /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 8d64c026579..53b8a1fa236 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 00000000000..d5efbb90261 --- /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 90c98e491df..cfd20d603cb 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 00000000000..553d0dd761d --- /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 b7fb63e939a..23bf571cc4b 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 5894376a065..7753c0f84dc 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 cf374894623..83a7518386b 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 154e82ec301..911eaa570d1 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 27b9b46297a..919fdb89afb 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 00000000000..fdc79cd04e9 --- /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 3e6886e74a3..dc7747a3dde 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 00000000000..d86aef5d51d --- /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 179e720abca..14c377783f4 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 d7498843b75..d88dbf235d8 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 20bcaaad643..400750b8fec 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 34c5fbdbaad..b3f18defca0 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 a319cf0e67e..10b985c11c6 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 00000000000..c6fccf44ba2 --- /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 3884170c2e0..82b1f09735b 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 091f7990989..e3451a9a058 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 00000000000..1e17e604c38 --- /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 36e14f97645..7c0caa68997 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 5f65978596e..67f0a6dc987 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 00000000000..4c13fdf68da --- /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 f980d5d2004..2efe036e309 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 00000000000..eac56eb486a --- /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 ac85a893c21..e0520cc442b 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 00000000000..ee4ea6b05df --- /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 f23bdb1379d..2aab8c8f8ec 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 00000000000..118cb50d8da --- /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 00000000000..29bb6935cf5 --- /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 2a013ba4772..ba24f5937fe 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 00000000000..8d90531191a --- /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 5aa2fc3609a..2af6adcd47d 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 8c54ea9fefc..179004c8e43 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 00000000000..84b9d25418a --- /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 00000000000..4296c2d05f9 --- /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 4eb010468f3..486d16cf25a 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 0d9e257feba..50b2728a93b 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 ec20df02074..d78113d7f9c 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 ef9fad14440..14768a05340 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 9b08ba9aee8..43d6a5f0b4e 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 00000000000..0c5be5614ac --- /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 00000000000..159cd52c341 --- /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 932b4b8a72e..7a85aed89fb 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 00000000000..ac4274b1fa3 --- /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 d3694cacab8..06b4294bae9 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 00000000000..7b56c616af1 --- /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 00000000000..b6ce644bac9 --- /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 00000000000..dc4207a45f9 --- /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 236cd0072a9..1d75ba96a7e 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 6d312fa98c1..3db8792484c 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 afae41f7d7a..c52a22e6970 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 77e55a889c4..98d1882d5e0 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 f8d181be5d3..84bb2dcffa3 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 ec18c9a65df..82c149c759f 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 932b4b8a72e..66c671f99a3 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 00000000000..467eb83fea3 --- /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 68dc0756725..bc2a518b65b 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 00000000000..4116fff076d --- /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 6ab597dbef1..0ba4436722d 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 74a0f8b1c9c..873867c9819 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 00000000000..26eae684761 --- /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 084048a625a..d778a301ee1 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 7963212e49c..e90f8e1cfe9 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 00000000000..045720cd332 --- /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 00000000000..0afb4c73e7d --- /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 8b6b7538ba8..5a5ec19d2b2 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 3d168bcce30..2e74dcad77e 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 00000000000..f7489f07d8f --- /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 28edec03d67..6040b52b670 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 00000000000..f8af97f7526 --- /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 00000000000..790e3e661a3 --- /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 89e49de710a..0a25890fd8a 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 00000000000..d86aef5d51d --- /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 00000000000..c64994cd0d6 --- /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 00000000000..cfb890a38bf --- /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 3304203c57a..488bf662102 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 00000000000..e63383e4d7c --- /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 00000000000..dbbd78229d2 --- /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 00000000000..a677332c3e1 --- /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 01a756a2ebb..7be0885bde9 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 00000000000..5e7c3559d90 --- /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 00000000000..802aef80752 --- /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 00000000000..62337802396 --- /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 902bb9167b2..7ba542d0cbe 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 00000000000..dfd1563d548 --- /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 af1c939be36..4137bf5b9a4 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 09aae548c62..a9f6016c6c1 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 00000000000..079c311cd0a --- /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 5ad1ca7ff6b..0e51da481d7 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 00000000000..b46de9ce8ee --- /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 ae60c2dcdba..51f86ab0e82 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 e10c6a8bbcc..d5432d5d60e 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 00000000000..f7b0e700c18 --- /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 8b7cd1004ea..08c70aa431f 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 00000000000..66c671f99a3 --- /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 01c296e59ac..e8568c1ec15 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 bb9a58d8127..8ffa238d816 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 eac953579c0..d300e57c1fa 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 a375e2c1f67..c6a9a255cb7 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 00000000000..88364743020 --- /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 c9384d82c38..ef5fb55e538 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 3f9df05d697..1feba202c0b 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 00000000000..c1e5cf3ac21 --- /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 e97b93d1e66..f95b3489f8c 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 00000000000..7caf7983714 --- /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 c7d73c344d8..e10e609a118 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 00000000000..1e898e15ce0 --- /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 00000000000..8df5069e431 --- /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 e0547d1f06f..06ddc17f942 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 00000000000..a97b0b3abd4 --- /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 efcaeb801d9..396f9f6ab67 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 00000000000..55888146567 --- /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 00000000000..5082256276c --- /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 8c2e276df85..8d986480ff0 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 e876d367575..1acfcef8cd3 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 00000000000..d0ed7981642 --- /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 00000000000..e29d809ebff --- /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 d658a07f4df..e1220006111 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 00000000000..a4c2dc620e3 --- /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 1ad4a767b32..e6f90beed31 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 00000000000..eae5b5e1484 --- /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 ec763ece1de..d66b0b1cf7c 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 befa822057f..c77ca3a02fe 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 abb894549cc..124d9d36c33 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 c38cfc72cc1..f1faffc85e1 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 00000000000..ab7d4be0a4c --- /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 00000000000..8722382b01b --- /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 d866e4d4ee2..f0a9a26bdb8 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 a148d1976ad..dbd20196984 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 39538d2bf52..a96222b15f3 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 00000000000..369064ba6cb --- /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 05cdeb6852b..dd2658a56db 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 00000000000..e6fd29a97df --- /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 60da2aebd3b..ace2e23312b 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 00000000000..93beddb92a8 --- /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 b9ea5d85166..40037f5e3eb 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 00000000000..429ae516c9b --- /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 00000000000..3640d2ac0a7 --- /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 b61651bf441..ad7889f7536 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 00000000000..210b33376d5 --- /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 00000000000..fac1d04755f --- /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 337a4d9f318..dfb4321dbc6 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 b4f025eaf6d..cae844d66ee 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 aae10144a66..04fe4682a42 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 00000000000..f16b424dee5 --- /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 062cc73de37..d1c658d7816 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 00000000000..47d51f44547 --- /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 9e8b24efa29..bdb2d4041ef 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 00000000000..ccc0fc7c477 --- /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 cd55de41a54..776b52e4d70 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 0e5b2151e20..cdf8d042b28 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 00000000000..566418e49a9 --- /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 cbaae7d2d6a..a0219eb309f 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 00000000000..66c671f99a3 --- /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 e1f66450b01..9200a7c2eac 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 5b6329a530f..9b6fe28f84b 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 00000000000..77da3eac5d8 --- /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 0bc966fdd6c..235008c7c31 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 00000000000..9b73cd590b2 --- /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 12cfbc75384..b6e08ffa7ec 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 00000000000..3fdd2b2db64 --- /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 00000000000..30221b1d790 --- /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 00000000000..b000a3b07c3 --- /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 3485b5304b4..bb4ddf9a67c 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 c4e2796e189..c3ad4dc1bf1 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 2f337742870..5e658d36102 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 71390930e35..8dccd6771cb 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 00000000000..f01e9d01465 --- /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 00000000000..db1bf2075f9 --- /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 110e7413121..5b5afff290a 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 f2467135d35..78407c85e22 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 08434f16c15..26e802b44c7 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 00000000000..7caf7983714 --- /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 e88b4ec3923..49b8b0cfb20 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 00000000000..70df08020b6 --- /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 ce15c28f47b..711ed2f62ff 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 00000000000..6858c6371c0 --- /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 00000000000..5e94ace2864 --- /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 00000000000..790e3e661a3 --- /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 87bf347795c..adbf635100c 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 00000000000..685c6dc5319 --- /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 00000000000..d252c078a2c --- /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 00000000000..3ccfca2f59f --- /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 c41610abb32..d86aef5d51d 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 a3dcc96c80b..d00cce3da9f 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 9f795e36734..c74dcf759ef 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 00000000000..c63ab2a1f27 --- /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 00000000000..d5efbb90261 --- /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 90c98e491df..cfd20d603cb 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 00000000000..451ec4459eb --- /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 00000000000..7164e5bb083 --- /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 e633eb9128d..f139d3fa5f5 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 00000000000..b928fae8a32 --- /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 af32cb3c3a6..9038912d008 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 00000000000..1b4b0047b66 --- /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 00000000000..ddbdda9d5a6 --- /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 00000000000..9ab59f40649 --- /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 477eb0ffa9c..3a11beed839 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 036af4e1c45..7965c3083d2 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 00000000000..4f05163182c --- /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 00000000000..2e1206f4807 --- /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 00000000000..33b2e29d518 --- /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 f214d3d7cfe..26e05764d2e 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 00000000000..4c7ead4c7d6 --- /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 2e12ac49a8a..347c3d8e88c 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 432a49cacf6..7a58875b9f2 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 f4852405726..1977a436365 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 153783b1b90..bfca7643b32 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 eaaa5717709..827c8e19066 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 7a4e9b8a02b..aa4a250041e 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 00000000000..e9a14f43238 --- /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 00000000000..2ba41d15fe8 --- /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 00000000000..4d3efb6fa67 --- /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 b1c853f5f2b..702aa6e33de 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 28ec7eb54f1..32688c432c4 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 00000000000..f73ab8555da --- /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 fdc42bcf303..e9edf1d94e2 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 2fb4b8cf720..bfb955dca5f 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 d9159ce954d..bffe92b154f 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 7d3df1b46aa..06353ca7846 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 f99a4781fdc..b905eb0c1e3 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 cc71ffde0e8..943b46849b6 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 9c474ca31b7..68e068c0fee 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 59a8b4cb52f..ae5ddf7549e 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 00000000000..e35968d4e89 --- /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 67b39f07f66..a0bf047d827 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 26523104070..9fe15726de7 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})?" + "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": "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 093611b2331..03f403b7936 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 00000000000..d905975f78d --- /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 325865ba87e..3258117a145 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 00000000000..20a7ba268bf --- /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 00000000000..7bf2a85b968 --- /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 00000000000..847cb96c0db --- /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 00000000000..6b6142af49f --- /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 00000000000..27ba000cef7 --- /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 c837be37bc3..9f3915dd39b 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 00000000000..21c367c3b87 --- /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 00000000000..0bb3eabad8c --- /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 c65686007b5..89b76484d0e 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 264ad237af1..de32d6c787d 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 00000000000..d7026fd7ef1 --- /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 648acb7e35f..c86ebf3cb34 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 3105455cb8b..605caa3e40d 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 bca1aeeaf3d..6db9e827831 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 f9028aae002..146adc6d4c7 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 00000000000..1e898e15ce0 --- /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 00000000000..21ab247c000 --- /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 a522da7febd..2f61296cff5 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 00000000000..a8d47f260b6 --- /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 00000000000..9eddde059a8 --- /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 94e5d3abef3..c4f0f5c65c4 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 c14cb64bf4e..6000966dc7e 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 a9a4a2a8b20..05e66acedcf 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 00000000000..062f816c14a --- /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 cbe6c6f78e7..ab43d965a8f 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 00000000000..e29d809ebff --- /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 f87b8b64576..9e89d9ff753 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 00000000000..da05a0b6690 --- /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 00000000000..3c3e1875f57 --- /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 43e2720b365..99728884edb 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 75ab523afd2..4064892a1fb 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 00000000000..20d831afd2e --- /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 07fc710408f..9d348536ec3 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 00000000000..4b579f14eaa --- /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 beeb45b9880..d57568ed7fc 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 b9580fd103f..dae2a14938a 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 00000000000..378809a995b --- /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 00000000000..fa9634e5b94 --- /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 00000000000..87327e1e441 --- /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 00000000000..ec60fefab42 --- /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 4cbb697371e..1778d39a7d0 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 90c98e491df..cfd20d603cb 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 e06bff43993..c07761309b8 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 28b8f70a8dd..e0904cf0683 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 00000000000..318aba882af --- /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 8c20db13830..b4ad9acae3a 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 f7979daff9e..9dc8810f499 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 43f00462515..6617d107471 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 e29d809ebff..7bc8288c2dd 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 21cfeb15a80..1dc0f525c4d 100644 --- a/homeassistant/components/debugpy/__init__.py +++ b/homeassistant/components/debugpy/__init__.py @@ -46,7 +46,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """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 cdbaecbeba5..b9dd7c374d0 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 63dc76be271..49f7ed18490 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -11,8 +11,8 @@ PATCH_VERSION: Final = "0.dev0" __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 967273a0f34..6b3176f6bbd 100644 --- a/homeassistant/components/canary/config_flow.py +++ b/homeassistant/components/canary/config_flow.py @@ -12,7 +12,6 @@ from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow 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 @@ class CanaryConfigFlow(ConfigFlow, domain=DOMAIN): 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 0c6aeabf648..c96126f43e2 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.components import zeroconf 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 987afe17012..0901d9a1e2c 100644 --- a/homeassistant/components/hue/config_flow.py +++ b/homeassistant/components/hue/config_flow.py @@ -3,6 +3,7 @@ from __future__ import annotations 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.core import callback 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 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ) 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 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class HueV1OptionsFlowHandler(config_entries.OptionsFlow): """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 @@ class HueV2OptionsFlowHandler(config_entries.OptionsFlow): """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 a91964ba3bc..921102b85dc 100644 --- a/homeassistant/components/iaqualink/config_flow.py +++ b/homeassistant/components/iaqualink/config_flow.py @@ -12,7 +12,6 @@ import voluptuous as vol 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 @@ class AqualinkFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 9316d4309c9..924ff5b278c 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.const import ( 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 @@ _LOGGER = logging.getLogger(__name__) 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 cdaab389dc7..c9e59107c1b 100644 --- a/homeassistant/components/notion/config_flow.py +++ b/homeassistant/components/notion/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 import aiohttp_client -from homeassistant.helpers.typing import ConfigType from .const import DOMAIN, LOGGER @@ -74,7 +73,7 @@ class NotionFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 8aa18502ba3..c7a1699a86c 100644 --- a/homeassistant/components/nzbget/config_flow.py +++ b/homeassistant/components/nzbget/config_flow.py @@ -19,7 +19,6 @@ from homeassistant.const import ( ) 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 @@ class NZBGetConfigFlow(ConfigFlow, domain=DOMAIN): 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 f2cc88538f9..b2afb55fc5d 100644 --- a/homeassistant/components/plum_lightpad/config_flow.py +++ b/homeassistant/components/plum_lightpad/config_flow.py @@ -11,7 +11,6 @@ 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 homeassistant.helpers.typing import ConfigType from .const import DOMAIN from .utils import load_plum @@ -60,6 +59,8 @@ class PlumLightpadConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 bcb881f3724..405474f5875 100644 --- a/homeassistant/components/ridwell/config_flow.py +++ b/homeassistant/components/ridwell/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 import aiohttp_client, config_validation as cv -from homeassistant.helpers.typing import ConfigType from .const import DOMAIN, LOGGER @@ -81,7 +80,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 3a3d1963e0e..44244e9c573 100644 --- a/homeassistant/components/simplisafe/config_flow.py +++ b/homeassistant/components/simplisafe/config_flow.py @@ -18,7 +18,6 @@ from homeassistant.const import CONF_CODE, CONF_TOKEN, CONF_URL, CONF_USERNAME 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 @@ class SimpliSafeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): }, ) - 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 3c758715205..d196bae8568 100644 --- a/homeassistant/components/switcher_kis/config_flow.py +++ b/homeassistant/components/switcher_kis/config_flow.py @@ -5,7 +5,6 @@ from typing import Any 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 @@ from .utils import async_discover_devices 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 58bb929e446..e1424453075 100644 --- a/homeassistant/components/tile/config_flow.py +++ b/homeassistant/components/tile/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 import aiohttp_client -from homeassistant.helpers.typing import ConfigType from .const import DOMAIN, LOGGER @@ -75,7 +74,7 @@ class TileFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """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 c91b08ca12f..3f08e7e692e 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 import config_entries 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) 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 415697670b0..993e070ffe8 100644 --- a/homeassistant/components/watttime/config_flow.py +++ b/homeassistant/components/watttime/config_flow.py @@ -18,7 +18,6 @@ from homeassistant.const import ( 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) 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 3bf4f7c6aeb..5100c2dee8a 100644 --- a/homeassistant/components/webostv/config_flow.py +++ b/homeassistant/components/webostv/config_flow.py @@ -20,7 +20,6 @@ from homeassistant.const import ( 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 @@ class OptionsFlowHandler(config_entries.OptionsFlow): 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 efb13fc3496..8fe7d8e8c72 100644 --- a/homeassistant/components/homekit_controller/button.py +++ b/homeassistant/components/homekit_controller/button.py @@ -53,6 +53,7 @@ BUTTON_ENTITIES: dict[str, HomeKitButtonEntityDescription] = { ), } + # 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 @@ class HomeKitButton(CharacteristicEntity, ButtonEntity): 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 8c20afcd06f..d88f896af31 100644 --- a/homeassistant/components/homekit_controller/const.py +++ b/homeassistant/components/homekit_controller/const.py @@ -74,6 +74,9 @@ CHARACTERISTIC_PLATFORMS = { 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 9c76adf52a9..135124cb207 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 @@ class HomeKitNumber(CharacteristicEntity, NumberEntity): 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 020f303ffaa..c501a9e6fb0 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 8eebcbda8f5..d83b4195241 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 5100c2dee8a..18338e86f1a 100644 --- a/homeassistant/components/webostv/config_flow.py +++ b/homeassistant/components/webostv/config_flow.py @@ -23,6 +23,7 @@ from homeassistant.helpers import config_validation as cv 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 @@ class OptionsFlowHandler(config_entries.OptionsFlow): 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 d3f5fec6826..70a253d5ceb 100644 --- a/homeassistant/components/webostv/helpers.py +++ b/homeassistant/components/webostv/helpers.py @@ -6,8 +6,8 @@ from homeassistant.core import HomeAssistant, callback 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 d6e2505b96c..1cbc72b43fc 100644 --- a/tests/components/webostv/__init__.py +++ b/tests/components/webostv/__init__.py @@ -11,27 +11,10 @@ from homeassistant.const import CONF_CLIENT_SECRET, CONF_HOST from homeassistant.helpers import entity_registry from homeassistant.setup import async_setup_component +from .const import CLIENT_KEY, FAKE_UUID, HOST, MOCK_CLIENT_KEYS, TV_NAME + 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"}' - -CHANNEL_1 = { - "channelNumber": "1", - "channelName": "Channel 1", - "channelId": "ch1id", -} -CHANNEL_2 = { - "channelNumber": "20", - "channelName": "Channel Name 2", - "channelId": "ch2id", -} - async def setup_webostv(hass, unique_id=FAKE_UUID): """Initialize webostv and media_player for tests.""" diff --git a/tests/components/webostv/conftest.py b/tests/components/webostv/conftest.py index 23c8687018c..05f1be66d00 100644 --- a/tests/components/webostv/conftest.py +++ b/tests/components/webostv/conftest.py @@ -6,7 +6,7 @@ import pytest 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 00000000000..eca38837d8e --- /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 34e6d96bfdd..b2b20677513 100644 --- a/tests/components/webostv/test_config_flow.py +++ b/tests/components/webostv/test_config_flow.py @@ -7,7 +7,7 @@ import pytest 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 @@ from homeassistant.data_entry_flow import ( 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"] + + +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()) - result3 = await hass.config_entries.options.async_init(entry.entry_id) - + 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 ef7c86327a8..fb8512d56f1 100644 --- a/tests/components/webostv/test_device_trigger.py +++ b/tests/components/webostv/test_device_trigger.py @@ -11,7 +11,8 @@ from homeassistant.exceptions import HomeAssistantError 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 eeb1e2fa0ee..8729576d869 100644 --- a/tests/components/webostv/test_init.py +++ b/tests/components/webostv/test_init.py @@ -8,11 +8,11 @@ from homeassistant.components.media_player import DOMAIN as MP_DOMAIN 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 a4071c741e5..c249b491d9a 100644 --- a/tests/components/webostv/test_media_player.py +++ b/tests/components/webostv/test_media_player.py @@ -64,7 +64,8 @@ from homeassistant.helpers import device_registry 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 315ae6ac919..a5188545737 100644 --- a/tests/components/webostv/test_notify.py +++ b/tests/components/webostv/test_notify.py @@ -9,7 +9,8 @@ from homeassistant.components.webostv import DOMAIN 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 f2fabe58ede..cbc72638ad9 100644 --- a/tests/components/webostv/test_trigger.py +++ b/tests/components/webostv/test_trigger.py @@ -7,7 +7,8 @@ from homeassistant.const import SERVICE_RELOAD 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 79e5e3f9fea..bceda1b453a 100644 --- a/homeassistant/components/systemmonitor/sensor.py +++ b/homeassistant/components/systemmonitor/sensor.py @@ -4,7 +4,7 @@ from __future__ import annotations 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 571111d1077..6ee0b8fefe1 100644 --- a/homeassistant/runner.py +++ b/homeassistant/runner.py @@ -15,8 +15,8 @@ from .util.executor import InterruptibleThreadPoolExecutor 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 a0f161b23ae..f8a38971a95 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 | 225 +++++------------- 2 files changed, 146 insertions(+), 160 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 00000000000..b793ee8656e --- /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 0597ad377cf..2e8fe309ff2 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 tests.common import MockConfigEntry +from homeassistant.const import CONF_CODE -@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 - - -async def test_duplicate_error(hass, mock_async_from_auth): +async def test_duplicate_error(hass, config_entry, config_code, setup_simplisafe): """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) + 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=config_code + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + + +@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" + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {"base": error_string} -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"} - - -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 27b9d720b0f..673e0ac4dfe 100644 --- a/homeassistant/components/sonos/__init__.py +++ b/homeassistant/components/sonos/__init__.py @@ -193,6 +193,7 @@ class SonosDiscoveryManager: 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 007348a66bb..707449e4002 100644 --- a/homeassistant/components/sonos/diagnostics.py +++ b/homeassistant/components/sonos/diagnostics.py @@ -130,5 +130,6 @@ async def async_generate_speaker_info( 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 4de0638d10c..5f06fe976ac 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -62,7 +62,7 @@ from .const import ( ) 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 @@ class SonosSpeaker: 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 @@ class SonosSpeaker: """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 8cade9b0aa5..a850e5a8caf 100644 --- a/homeassistant/components/sonos/statistics.py +++ b/homeassistant/components/sonos/statistics.py @@ -9,13 +9,49 @@ from soco.events_base import Event as SonosEvent, parse_event_xml _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 @@ class EventStatistics: 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 6dd3236ea97..61e2b50d7aa 100644 --- a/homeassistant/components/tradfri/__init__.py +++ b/homeassistant/components/tradfri/__init__.py @@ -57,12 +57,18 @@ LISTENERS = "tradfri_listeners" 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 async_setup_entry( 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 async_setup_entry( 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 2cbae147a78..0459651e693 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 @@ SENSORS: tuple[WhoisSensorEntityDescription, ...] = ( 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 @@ SENSORS: tuple[WhoisSensorEntityDescription, ...] = ( 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 @@ SENSORS: tuple[WhoisSensorEntityDescription, ...] = ( 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 @@ SENSORS: tuple[WhoisSensorEntityDescription, ...] = ( 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 bbda3b101f5..ef14750356e 100644 --- a/tests/components/whois/conftest.py +++ b/tests/components/whois/conftest.py @@ -3,7 +3,7 @@ from __future__ import annotations 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 6701d8ed20c..3cd9efc801d 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 b0e9862eb64..e824522ed09 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 29845775409..4f27ff4ff4f 100644 --- a/homeassistant/components/unifi/diagnostics.py +++ b/homeassistant/components/unifi/diagnostics.py @@ -31,24 +31,42 @@ REDACT_WLANS = {"bc_filter_list", "x_passphrase"} @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 9d1c4543857..70485ac0c5f 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 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator): ) 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 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator): connection_type="", ip_address=host["ip"], ssid=None, - wan_access=False, + wan_access=None, ) mesh_intf = {} @@ -352,12 +360,10 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator): 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 @@ class FritzDevice: 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 @@ class FritzDevice: 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 0726741845a..fec760fbe7a 100644 --- a/homeassistant/components/fritz/switch.py +++ b/homeassistant/components/fritz/switch.py @@ -477,10 +477,17 @@ class FritzBoxProfileSwitch(FritzDeviceBase, SwitchEntity): 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 4d9b769791c..cc82fd536e7 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 f52cc0c17d4..f96f9d828bb 100644 --- a/homeassistant/components/edl21/sensor.py +++ b/homeassistant/components/edl21/sensor.py @@ -15,7 +15,14 @@ from homeassistant.components.sensor import ( 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 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( 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 @@ class EDL21Entity(SensorEntity): @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 733693720a0..afba19c82d9 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 157acea0f53..2a2b8567f27 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 07ce89ccd05..3daa7a710bd 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 26055b964f8..18f19265f59 100644 --- a/homeassistant/components/homekit_controller/config_flow.py +++ b/homeassistant/components/homekit_controller/config_flow.py @@ -4,6 +4,7 @@ import re 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 @@ from homeassistant.helpers.device_registry import ( 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 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 1e011eaf15d..88706e6fbd0 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 @@ class HKDevice: 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 33b5b15698d..c9354297a43 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 00000000000..bc0dddef47c --- /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 30ea49a359c..6465b542f36 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 2a2b8567f27..7a1cc0e98d3 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 3daa7a710bd..5b6c25c2a7b 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 b793ee8656e..d9e6d46c2eb 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 00000000000..ca44c0674f1 --- /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 00000000000..073d51b0538 --- /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 00000000000..2b617bb8663 --- /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 00000000000..56731307e42 --- /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 00000000000..d2c2866bf5b --- /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 88706e6fbd0..08b3e9823e3 100644 --- a/homeassistant/components/homekit_controller/connection.py +++ b/homeassistant/components/homekit_controller/connection.py @@ -494,7 +494,11 @@ class HKDevice: 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 d88f896af31..9ec991c0d88 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 @@ IDENTIFIER_LEGACY_ACCESSORY_ID = "accessory-id" # 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 ef77d5bc652..802c150b981 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 802c150b981..0891d6209b7 100644 --- a/tests/components/homekit_controller/common.py +++ b/tests/components/homekit_controller/common.py @@ -10,9 +10,8 @@ from typing import Any, Final 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 @@ from homeassistant.components.homekit_controller.const import ( 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 @@ class Helper: 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 9591eb27b6f..33a1ebdbafe 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 datetime import timedelta 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 5694be5f955..2804ffff824 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 aiohomekit.model.services import ServicesTypes 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 e0b23775c4d..d83beb07df3 100644 --- a/tests/components/homekit_controller/test_binary_sensor.py +++ b/tests/components/homekit_controller/test_binary_sensor.py @@ -6,13 +6,6 @@ from homeassistant.components.binary_sensor import BinarySensorDeviceClass 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 c501a9e6fb0..131fed572f7 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 07a5025ac88..9ca45fd53ac 100644 --- a/tests/components/homekit_controller/test_climate.py +++ b/tests/components/homekit_controller/test_climate.py @@ -22,21 +22,6 @@ from homeassistant.components.climate.const import ( 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 45514b29122..35e6933f2cd 100644 --- a/tests/components/homekit_controller/test_cover.py +++ b/tests/components/homekit_controller/test_cover.py @@ -4,23 +4,6 @@ from aiohomekit.model.services import ServicesTypes 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 d66ce81d534..252e7f87bed 100644 --- a/tests/components/homekit_controller/test_fan.py +++ b/tests/components/homekit_controller/test_fan.py @@ -4,15 +4,6 @@ from aiohomekit.model.services import ServicesTypes 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 0af795e2ce9..ea32b1931c2 100644 --- a/tests/components/homekit_controller/test_humidifier.py +++ b/tests/components/homekit_controller/test_humidifier.py @@ -7,25 +7,6 @@ from homeassistant.components.humidifier.const import MODE_AUTO, MODE_NORMAL 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 f4950512063..8d094c4eb70 100644 --- a/tests/components/homekit_controller/test_light.py +++ b/tests/components/homekit_controller/test_light.py @@ -10,12 +10,6 @@ from tests.components.homekit_controller.common import setup_test_component 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 15e645bf181..8f996cea8e3 100644 --- a/tests/components/homekit_controller/test_lock.py +++ b/tests/components/homekit_controller/test_lock.py @@ -4,9 +4,6 @@ from aiohomekit.model.services import ServicesTypes 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 44c53af02da..d45e785f315 100644 --- a/tests/components/homekit_controller/test_media_player.py +++ b/tests/components/homekit_controller/test_media_player.py @@ -8,11 +8,6 @@ import pytest 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 d83b4195241..a8a40bfa732 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 ea22bf68c54..6cc7f5336b4 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 4c57d94b2b8..fc1a48f0f5d 100644 --- a/tests/components/homekit_controller/test_sensor.py +++ b/tests/components/homekit_controller/test_sensor.py @@ -7,15 +7,6 @@ from homeassistant.components.sensor import SensorDeviceClass 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 5c737e63edc..fbea04171cb 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 a0501a41959..9212c7d4247 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 dc83ae8dc5c..27f72830f87 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 7ad57a0ec68..4ff498f98d6 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 f4ac44d3b60..469dd81ff91 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 00000000000..1e9cca9b30b --- /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 7432d0df484..cb02c7e0ad5 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 aa5c22db175..33d9f11e95e 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 4bde04e1695..09498932c20 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 e7a56e8f736..1ab13f21d96 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 aa33b469b1b..1fd86188063 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 dcf9a44ea4a..f2ec82e1a20 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 51e8d3fd84e..6a0c8800b02 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 e4203d7c177..5dab2c95651 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 30677a85041..29133caadb8 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 abe96f8cccc..e1f2fe097a6 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 acc62eaa7d8..fa24093b419 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 fb9b1f4c79e..e710bc87da0 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 b9db19d0346..b1d8b655029 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 00000000000..07ea1099cc1 --- /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 ed19402786f..b0aeb95fcb9 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 07e005cba03..70d7a68ecc4 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 8caf8d08cec..bdc65371ac7 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 d6ddde76595..d012b1695f3 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 db96ef3d0a3..d1e0d1e1cb6 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 03e813c18ff..2d960e17df2 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 00000000000..2e0dc187368 --- /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 00000000000..6e8eb4d0ac8 --- /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 9f6290dc492..19c0bce83cc 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 b5b2f9fc0c2..86e9a29dd12 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 5e0497e76c0..79fee5f02ec 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 58279f9bff9..8df3d821f93 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 67dcf497bc0..bb8bb1f7819 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 07f99344a4a..06982ade622 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 2d6380e59be..1577ad4436f 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 61185dc14f7..6582c5b60c4 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 7d9ce5c434d..dcaa594cd13 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 d81a4031129..8fb79bcefa2 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 f4a924d02db..f895082e233 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 c2b3b7e1568..9dbd275d2bc 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 30c78d0a187..447658433e5 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 8225c4088f7..3f3bbcac514 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 ad86023f818..635a7373b75 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 7c25606016e..3208a509ec4 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 859c87db3fb..d8fec75b0f4 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 65502dcaa23..853a553cd42 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 ff6ede166a9..cd16bb3cec9 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 03a750a186d..8ad46475a71 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 4ca2f04550e..2e052970d63 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 0f8d92749c4..c423002359e 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 3bc015b6b55..5a672767617 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 5624d3765d3..440558cddd7 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 86cf9781d3a..b4b82c1fe18 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 2e596948dc2..8300c0db0b3 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 4916cbdfe49..f8a74157ab3 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 7bc600a3644..bd6d47af018 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 0cafe568193..e5a372176aa 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 0306932f146..33113d12881 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 8b09d9885c2..789b09ebefc 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 1ab872ffb71..9ce1013644f 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 fca0b1a103b..0d47e41440b 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 25ded1810fe..3f3d5154757 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 74327f3ebd6..6664c756e16 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 369064ba6cb..708a5072254 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 5f8d69f5f29..5ee7dbfde34 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 db1fff5cd04..6e31e42ed49 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 687bfdf71f9..54de15d1f7f 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 e920caf2a87..fc745aaef39 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 00000000000..7e9a1f71c06 --- /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 8a763643004..c591abe2b3b 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 425ec2733b8..a8e383dc7b7 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 b0989763567..d40c11d57dc 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" } }, + "country": { + "data": { + "country_code": "C\u00f3digo do pa\u00eds" + } + }, "user": { "data": { - "api_key": "Token de acesso" - } + "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 2eebb526015..fc2068cb01a 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 4c6c4266476..6596135208b 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 c09810d8a43..f6fc6c6ef62 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 d69f8206c46..4bb7d51e464 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 3403666dfb9..81689ea1122 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 64f05f01903..df4e446837e 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 feda280cc3f..03004cae304 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 8364f0bc94b..49290be4ceb 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 00000000000..2530ff3b4ca --- /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 a39a263484b..084c7dd3c18 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 c20638a4a61..762fb96fd05 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 58d60891613..c2136958ffb 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 edffd23f3af..94a1f632d78 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 d86aef5d51d..ce21e4d51c8 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 00000000000..b12cbdbb1db --- /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 0cb6f57eae2..199db0c326c 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 82fe7daea00..3d2617d25e5 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 43954a1f032..7b5c5a96464 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 625d2db78dc..5186a1882f1 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 f317d16eb41..98fa2d6e3b6 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 58ed851546d..0df5a60e565 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 bf252c8ef80..4b2e6a1fc65 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 b0aebece7b4..6528bdb5a61 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 edb649341c8..5eaa736cd5c 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 e34e7593c77..3f2c479df8f 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 95b4ef549a7..911a93db1b8 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 d783704c0a9..072cf6011ea 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 e29d809ebff..f7dfd4cf080 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 35f7967ccac..3174c06c802 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 7cf7df53429..0d411f1fdd1 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 55722d53aeb..23469f0fd26 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 065c29ab9ab..8197121b5d5 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 10441872c51..4cc692371d3 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 5bb2846243a..d86e777877e 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 efdc82ab438..ffc9c5ba4ec 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 b2cefe66206..9db13c83fbc 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 ff6ede166a9..80e47d1f10c 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 864ae263dba..139a17577e8 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 00000000000..c8d85790fdd --- /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 9ab59f40649..c0d65a7934f 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 5c3e2d6eb6e..01f7447c8ae 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 223bb36392f..d4e296b6b4b 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 f99ab671b8e..6325a126823 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 ec60fefab42..c14278182a5 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 6a637c735f7..737bc5020af 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 159cd52c341..5dda4dc69dc 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 870003bde5d..371686bbf98 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 34892b7c476..87e64d0db17 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 07f6bbf8c7b..aa4474a782f 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 ef519660db8..97145dd8a60 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 2d9c2ad919c..45b55aa5324 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 d529509749c..164c5cde793 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 2601b89b2a1..444c72d3d5d 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 8722382b01b..e3535fb54ff 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 17c9f8afe60..a9027c5bfd6 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 35950f57d3b..dc63fa1baf8 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 82b12411670..36560315ee7 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 aad75b3bed0..ad6cca066c4 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 a40bc1321a1..1b768604bef 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 8037db16e53..b33dce1e6fe 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 42fc274e0a8..aa0a9b03737 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 1e898e15ce0..021ab5c902a 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 71a36bb8359..2e20c6b5da9 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 9076a635244..a28f063ab6d 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 7693e15a9ec..3e3884cbc41 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 7bfce93a571..55919515397 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 70f90c5b5f4..da83db69451 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 4b01a4755c3..ea66c718a27 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 ba80a197994..e653cb99d43 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 59ed1cf6b5b..30a2acfdadd 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 95ac686e86e..226c388532e 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 54a14afb764..b38d3b66c7c 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 6f79c486519..5faa4ea054a 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 c6613936401..b8d85b6960b 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 2228a8031ac..39472c3b420 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 8dbc3289c81..f7cb2ac22e4 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 00000000000..fd43f179032 --- /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 1953aa13e2e..d081c897d38 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 81137fe0bdc..7ecea696702 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 fd074fcc0f8..99b63826b30 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 d648ff4e8e8..8986006fdeb 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 9365f5ac690..b0896991097 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 c04d58020d6..4674c074763 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 d90b648760d..26bfba4eaea 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 8ef272f0670..a3204d7e554 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 5b0b6305cdb..25cae99e3b0 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 d5efbb90261..1778d39a7d0 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 292861e9129..5c2489c2a18 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 e956f89d381..29222d32df4 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 14615cff002..f0996e6b84e 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 f8ca0b40187..cbdb6e3453f 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 bcf50b5d3da..c60d9d8ec47 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 7686665e6f6..4b3f264ddeb 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 00000000000..b157725600d --- /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 c0a5fdf04bf..8ea94d7a045 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 d529509749c..1b98cc41a7a 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 900882513d5..d7da075345a 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 ef6f9993b11..5f5f7e857c2 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 ea479059ebb..54cd4aece0b 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 85e6cfdec61..3b7340be7c7 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 00000000000..f30a1775e0e --- /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 4b0a4a64a71..c8b3c037c0e 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 1c2e3094ec5..ad8aab120f4 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 47ee5b8630a..67b89e51b08 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 00000000000..68fc27ab80e --- /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 00000000000..15be755add3 --- /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 00000000000..d330549f208 --- /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 00000000000..b2bb2e8e3e2 --- /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 101cdd83c48..bff773d58bd 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 c0d1be424a5..61f8a62a6c1 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 2916d86b242..b1abeef9927 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 7922f363bda..f16a6c71637 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 821b2f6e72b..7b69fce212d 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 a2cc864aeff..05dec678318 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 9505a6a0838..8d96d698209 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 2783abe00e6..bb4b6c00177 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 b98d170336f..b170cb59882 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 f10ded2b0b3..325caaa27f6 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 7406eb095f2..a0d75722f0d 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 b855d755726..fc71e748a15 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 fe7c4d91253..0c3363a3799 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 81616a31dd7..a3a63f532cf 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 82006860db3..727b21f43be 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 32dec17701c..239afa3b7e4 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 8ea8f2b1970..46bc1de9d62 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 c2b366b4d0f..409ac9d6283 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 302d8c3676a..747e3a30176 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 5f8d69f5f29..5ee7dbfde34 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 79d60b8a2f7..9fec74f379f 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 f2af40324eb..b6022ba8124 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 fd3619849c2..7da66ff568b 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 9ab59f40649..c55992a3e97 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 9290114899a..3774d178025 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 13483418ffa..f8ef8d27cd7 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 00000000000..148e4b91c01 --- /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 00000000000..6b76fb0e6bc --- /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 00000000000..c14e2f83800 --- /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 34a9644e9d0..b4257ea668c 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 00000000000..cfcf3e0c458 --- /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 d377b19f586..b644b6c1bfc 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 c874b8654f5..e50bc5cb26b 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 ae7a7293429..d9055af4b36 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 8ab6c1e1664..e29c169a26e 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 806cea1df9b..c5cd5d0dcc9 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 937edfdd914..c143db7cbd1 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 93beddb92a8..2d0b50c0640 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 83a11ef82fe..a4744a41a2c 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 0d0bbffce14..9b68bd02d2f 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 a5343784fab..0e8e3402961 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 321f7f6ffef..be8f6cbdfd3 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 24ba6ade2c0..b49b487ab0d 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 92cd3744d94..6a3170ffde7 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 b829ba6e92b..a670c5a41be 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 9ce2cf2399e..955386f2098 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 d5efbb90261..1778d39a7d0 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 292861e9129..5c2489c2a18 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 00000000000..9898533ea72 --- /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 83a7518386b..f67284d8b5d 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 8c32e79533d..556729e895b 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 190ed3f52bd..f70830601c7 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 919fdb89afb..f884baf9b3c 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 fdc79cd04e9..e41fc57329e 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 52aed47fa20..d8ec556180f 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 89003e78a9d..a4f03bde29f 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 ed39d00430b..0a459e566c5 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 400750b8fec..d134a5113f4 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 d9a47130871..ccfac69f0a9 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 f9c4e12214c..02ce765e7d1 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 10b985c11c6..3086d6560f1 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 c6fccf44ba2..38a6ae11358 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 00000000000..2ff25d17161 --- /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 e3451a9a058..28a85a8820d 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 1e17e604c38..907a396d5e2 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 cacb7e92502..58818dd99de 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 dea33946af5..5e84c62a314 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 7c0caa68997..7985d9ddbfb 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 d999b52085a..0c31d070bec 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 4c13fdf68da..7b28450bfd0 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 6ad22742533..5fae215f4f9 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 2efe036e309..147f66ec0e7 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 eac56eb486a..6cd33d9fbb1 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 dd50a5ac784..6759159b1db 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 e0520cc442b..d87561136a6 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 ee4ea6b05df..d72a8d1ccfd 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 2aab8c8f8ec..456c6eef17c 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 118cb50d8da..c81109b8939 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 29bb6935cf5..3e0fc4b79d3 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 ba24f5937fe..0fb66a063bd 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 2af6adcd47d..d71651e98e8 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 4c211f4bc53..f108db65cad 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 84b9d25418a..39394d0752e 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 4296c2d05f9..07a68e2fb84 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 d72413d8e86..28c82949398 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 486d16cf25a..2935c6510be 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 50b2728a93b..dcabdbd16e5 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 d78113d7f9c..2ede694dcba 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 14768a05340..526fe072cf7 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 b8cbab32b14..b684595b170 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 0c5be5614ac..341389ea117 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 159cd52c341..fe08f78aa73 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 7a85aed89fb..75d340c7571 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 ac4274b1fa3..468acf70588 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 7b56c616af1..270e080701c 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 b6ce644bac9..3946a794711 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 dc4207a45f9..684f7cdff22 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 1d75ba96a7e..201cd7c02b5 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 3db8792484c..54b558493b8 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 f2ee64e7fc4..0d105c59c05 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 98d1882d5e0..b47c0ea3646 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 b8c439edfde..9d6065a4841 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 82c149c759f..4789dcc042b 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 66c671f99a3..e9cce64aef9 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 467eb83fea3..43b7a296c21 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 bc2a518b65b..4c31da85534 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 4116fff076d..c22d3e06530 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 26eae684761..bf058495cb9 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 75652397406..288856ef8a1 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 045720cd332..e00d14b479e 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 00000000000..b4527702744 --- /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 5a5ec19d2b2..d6be7b41044 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 f7489f07d8f..69c0c575eca 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 eba15cca19c..a7f7e8b3f07 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 790e3e661a3..6a48db65df5 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 21ebf6f4faf..6c65ff99dbb 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 c64994cd0d6..c219c32f8e3 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 cfb890a38bf..401452bcaf5 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 488bf662102..d5586b2dd2b 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 00000000000..44ef054dcea --- /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 f15bc7bdc0e..11f543797fe 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 a677332c3e1..cf4ed0846d2 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 7be0885bde9..1fe9216bdad 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 5e7c3559d90..dd88767bb61 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 6ff74c5a61e..b2bf92f329f 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 7c64d862e2f..e76f534350b 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 802aef80752..545ddf77c8d 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 00000000000..6c4c93ca753 --- /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 f1471f4f5b6..aef0b1e0394 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 7ba542d0cbe..3ec82aa83d5 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 dfd1563d548..4b73ecca8b9 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 faec1e6977b..998478a9cc8 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 65189e6b0be..74baf0fe106 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 4137bf5b9a4..79c42db20e3 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 e6a6fc26068..04cac3dd6ec 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 d365534110b..f30b504897d 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 079c311cd0a..8ec256d1d75 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 f8fb161aa1c..f662bc978ae 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 b46de9ce8ee..777a1fc5633 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 51f86ab0e82..8cc89f6215b 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 f7b0e700c18..a4da1d92ed6 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 08c70aa431f..3de821afe8d 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 66c671f99a3..c11bc3fa965 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 bc23114f672..85fc79981ce 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 8b3f030b72f..0842ca6da74 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 7dc3eaf6fb7..83e2874ed73 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 e8568c1ec15..4b57d3f984e 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 a4f7de7c6be..6e740a68cdb 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 09b88d2578b..4a720d6cb77 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 d300e57c1fa..ea74d2b173b 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 c6a9a255cb7..f53f6c7c379 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 88364743020..4213b842e46 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 ef5fb55e538..a940c67daf9 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 798f76e4f6b..c4d23a6055d 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 c1e5cf3ac21..8b2e2bddda0 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 f95b3489f8c..1da49f708d9 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 5594895456e..b1d6150a318 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 1e898e15ce0..9d5f4715fd1 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 8df5069e431..71d9df7c175 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 06ddc17f942..7938cbb6241 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 a97b0b3abd4..39bcb9258c6 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 396f9f6ab67..e5754180a7c 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 00000000000..4e3e9483945 --- /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 55888146567..c1c53e065dc 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 5082256276c..e40f41a6152 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 1acfcef8cd3..6359b1b6ae9 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 d0ed7981642..57de88fd6e9 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 e29d809ebff..0df3b63a2f8 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 18d984f5c68..47ba3d7eda7 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 e1220006111..15d9b0d7c76 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 a4c2dc620e3..28054eac1c5 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 eae5b5e1484..6f867a22a55 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 1b0938b8b70..65cca65679c 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 124d9d36c33..e3bbe6cd9d0 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 ab7d4be0a4c..53659ab672a 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 8722382b01b..a278ec20ec2 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 00000000000..793cd468f33 --- /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 f0a9a26bdb8..408bbc915c0 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 dbd20196984..ee0a29eac89 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 369064ba6cb..f886cab722a 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 b60160e1c4e..39b0dee9bdb 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 57d4fd851d2..6b3344f2b6b 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 e6fd29a97df..78560798862 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 429ae516c9b..407e9d94d0a 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 3640d2ac0a7..ee99fa03406 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 00000000000..1eabd618ce3 --- /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 ad7889f7536..3544bd22dc3 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 4971b9f4494..36fd50cfdcc 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 dfb4321dbc6..72e1c7ba023 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 cae844d66ee..21f1e3ef91a 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 01da0308851..124ac4543ed 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 47d51f44547..8b8ec5ea020 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 ccc0fc7c477..a113717859f 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 776b52e4d70..35cb5786fd2 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 cdf8d042b28..f64e478a485 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" }, - "title": "Preencha suas informa\u00e7\u00f5es" + "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": "Configurar SimpliSafe" } } } diff --git a/homeassistant/components/sma/translations/pt-BR.json b/homeassistant/components/sma/translations/pt-BR.json index 566418e49a9..21017fe6fc3 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 a0219eb309f..dfd6b31fe2e 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 9200a7c2eac..ef205c53827 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 9b6fe28f84b..a4a80fb29cc 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 77da3eac5d8..4eedb6d7a3b 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 9b73cd590b2..05e211760f2 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 be271a2b6e4..0105742da5f 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 3fdd2b2db64..f005c2913ef 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 30221b1d790..b935d245da9 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 00000000000..70aee01ce0f --- /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 00000000000..4e0c400148f --- /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 00000000000..bc7d29b12f9 --- /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 00000000000..35980b52f40 --- /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 bb4ddf9a67c..fb1af9db783 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 5e658d36102..8ad5fac9044 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 ebf7e41044e..207169ad6b0 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 f01e9d01465..12efd378984 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 db1bf2075f9..f6b8b63781a 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 5b5afff290a..0a6fcbb52c2 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 78407c85e22..527d0046b7c 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 aff6c9f59b1..64d4af145c4 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 7caf7983714..0498a2fad6d 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 89ef24440d1..e54ed1da92e 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 70df08020b6..3c281b4754c 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 b8be2a14887..52e7158c40a 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 6858c6371c0..c49fbfabfa0 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 5e94ace2864..28ca34f0818 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 790e3e661a3..b0dbfe9c6d0 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 5267aa2a575..144e40f15bb 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 3b73793804a..d9141c8b5cb 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 ccd7379c346..926f3cc4b9d 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 685c6dc5319..b099c4b36f7 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 d252c078a2c..5ad1d9f9a81 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 3ccfca2f59f..e88a9e883c0 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 dbc4dc19f37..71d3b0f6b8e 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 d00cce3da9f..cb73ce3c5cf 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 c63ab2a1f27..bf9cb746dcb 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 d5efbb90261..1778d39a7d0 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 451ec4459eb..08f66569d93 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 7164e5bb083..0872de05be9 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 8740308faf0..801c1d7fe82 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 f139d3fa5f5..ddde3e1e29d 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 b928fae8a32..ed1bcd01ded 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 9038912d008..68cf24fe5df 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 9ab59f40649..6fa1f064e88 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 6639a9c9626..5b57f950866 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 7965c3083d2..1c94b12cc59 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 4f05163182c..da2c9d50f34 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 00000000000..bf736e09664 --- /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 2e1206f4807..7ff66347009 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 33b2e29d518..e3a4aa67c02 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 4c7ead4c7d6..c86e10b4e8e 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 00000000000..61ee349aa2f --- /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 347c3d8e88c..98220340205 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 7a58875b9f2..47dfe5437e0 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 1977a436365..a034b44b761 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 cfeaf049675..abbfc076f7e 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 4338175fda6..c635e23fbe0 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 0b4563d69fc..45a70a42b43 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 827c8e19066..1fc86514bd1 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 5bfb1714a79..4d400941016 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 e9a14f43238..d33bdf4f9aa 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 702aa6e33de..5cf3afc282d 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 e9edf1d94e2..3353884ef33 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 bffe92b154f..242d1e9ee08 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 575a28b2dc0..629e98f6986 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 57d4297fd68..89ea2c39090 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 0961f6a29fd..4393efb7859 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 06353ca7846..e32b8ebcd3c 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 9f575f3f5f4..c6da7570d5e 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 00000000000..3e802b0e97f --- /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 00000000000..b8e8beeb094 --- /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 1d2709d260a..97616e5f388 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 45930c49e7b..84ea72878e7 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 3d31175d2de..cf180123c98 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 68e068c0fee..4f6e33f0f7a 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 8ea0ce86a37..0ba735ba9c2 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 e35968d4e89..3aff4eb867d 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 cfc6a2fc8b4..4c3917b4cae 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 a0bf047d827..2419418479b 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 a7417a8e50d..090624f2009 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 9fe15726de7..1f26b952998 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 03f403b7936..402b81140f9 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 d905975f78d..0fadb90bf5a 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 7d07ec8da09..cc89a22092a 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 3258117a145..a1544981ea9 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 20a7ba268bf..0d9bea96b12 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 00000000000..e9bccabf736 --- /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 00000000000..8aad2b8b56f --- /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 77a38f3b298..e149d944f93 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 847cb96c0db..729e5257db8 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 27ba000cef7..c22a92e1808 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 00000000000..361b0487617 --- /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 21c367c3b87..1fc733bfd55 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 0bb3eabad8c..2129822ace5 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 7f6b3a46b15..1649357f4ff 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 d7026fd7ef1..01504272dac 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 605caa3e40d..8766261955f 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 13b26b44b76..99414ed3b6f 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 6db9e827831..785bbd75294 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 146adc6d4c7..0a26a6aaf7d 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 1e898e15ce0..487710baf01 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 21ab247c000..3fb6428603a 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 00000000000..28e234b4d74 --- /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 2f61296cff5..286a714eac4 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 a8d47f260b6..54ade45119b 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 64a81da9b35..bf5f96d8491 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 6000966dc7e..59e6bf2e3b0 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 1217d664234..2a705d370f4 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 7102e95a25d..66a7868a265 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 062f816c14a..cf4b5334c20 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 ab43d965a8f..226e5399cdd 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 e29d809ebff..5da689b9b74 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 9e89d9ff753..6a067498f1e 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 8423f2d3f48..d1ba5b75ec3 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 da05a0b6690..c922f86d776 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 00000000000..0323f9f7960 --- /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 99728884edb..617a2398ae8 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 2af363e8f1c..7c2cdca7bf8 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 20d831afd2e..7f788c1ebb8 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 a1b3f8340fc..1828c0737a6 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 4b579f14eaa..5e188e54565 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 d57568ed7fc..9e966a541d5 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 00000000000..c4c1735bff3 --- /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 dae2a14938a..332c91ed447 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 378809a995b..eee52e2182b 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 fa9634e5b94..dfbd33784b5 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 87327e1e441..2c54af41b25 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 292861e9129..5c2489c2a18 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 c07761309b8..2e5aec0d1cc 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 7bd62cf26e1..f7206911534 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 00000000000..9662e6160e4 --- /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 318aba882af..a7fada70a83 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 b4ad9acae3a..079f1ab8593 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 696c0caccd2..3c5b49681c8 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 7bc8288c2dd..b8fadd65089 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 c2ada4190b3..e6b4c4aceab 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 ad423e84fa5..6efabe537f7 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 9464305cd16..4d4738c39f9 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 eb1094b7675..823d37267d6 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 7a1cc0e98d3..a706ec5de0d 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 5b6c25c2a7b..0f63722cc65 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 5fb974fa5c7..fccd8979631 100644 --- a/homeassistant/components/powerwall/__init__.py +++ b/homeassistant/components/powerwall/__init__.py @@ -5,6 +5,7 @@ import logging import requests from tesla_powerwall import ( AccessDeniedError, + APIError, MissingAttributeError, Powerwall, PowerwallUnreachableError, @@ -131,7 +132,28 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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 a97265ca244..9e4557276ca 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 a706ec5de0d..f7260088e0e 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 0f63722cc65..08eaf12cf0c 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 cd15d77629c..71a86f1e397 100644 --- a/tests/components/knx/conftest.py +++ b/tests/components/knx/conftest.py @@ -13,7 +13,7 @@ from xknx.telegram import Telegram, TelegramDirection 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 @@ class KNXTestKit: # 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 d6c2ec98b20..83b5a9988c7 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 e8344e0a655..1a91abd9a99 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 c685c991e17..c1a08a3f0c3 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 952729caf09..72716f15dfd 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 efab893be42..ff1962aed1b 100644 --- a/homeassistant/components/flux_led/__init__.py +++ b/homeassistant/components/flux_led/__init__.py @@ -14,7 +14,7 @@ from homeassistant.config_entries import ConfigEntry 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_setup(hass: HomeAssistant, config: ConfigType) -> bool: 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 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # 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 3f74090f075..fcd4ecc3adc 100644 --- a/homeassistant/components/flux_led/button.py +++ b/homeassistant/components/flux_led/button.py @@ -64,8 +64,8 @@ class FluxButton(FluxBaseEntity, ButtonEntity): 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 c06070002d4..5946ab817de 100644 --- a/homeassistant/components/flux_led/entity.py +++ b/homeassistant/components/flux_led/entity.py @@ -7,19 +7,28 @@ from typing import Any 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 @@ class FluxBaseEntity(Entity): """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 @@ class FluxEntity(CoordinatorEntity): 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 85b74616b32..4534c45e228 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 7c607219901..d7fad9cf0e6 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 701465da036..3b78baa782b 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 @@ class FluxPowerStateSelect(FluxConfigAtStartSelect, SelectEntity): """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 @@ class FluxWhiteChannelSelect(FluxConfigAtStartSelect): """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 6d67ced1fe2..18d1aac5506 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 f8de86d3340..ee004fc2250 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 @@ class FluxRemoteAccessSwitch(FluxBaseEntity, SwitchEntity): """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 7981f2cef11..de655c2e6ad 100644 --- a/tests/components/flux_led/test_init.py +++ b/tests/components/flux_led/test_init.py @@ -7,10 +7,16 @@ from unittest.mock import patch 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 83a76311e8a..67603544c5d 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 a4b23f47fcc..d0d71cacbe1 100644 --- a/tests/components/flux_led/test_number.py +++ b/tests/components/flux_led/test_number.py @@ -41,7 +41,7 @@ from . import ( 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 6df276e5011..b2a88b00fe0 100644 --- a/tests/components/flux_led/test_select.py +++ b/tests/components/flux_led/test_select.py @@ -14,6 +14,7 @@ from homeassistant.components.flux_led.const import CONF_WHITE_CHANNEL_TYPE, DOM 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 ce2855a53ed..cb0034f8d36 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 @@ from homeassistant.const import ( 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 0459651e693..6df920f385c 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 @@ SENSORS: tuple[WhoisSensorEntityDescription, ...] = ( 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 @@ SENSORS: tuple[WhoisSensorEntityDescription, ...] = ( 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 @@ SENSORS: tuple[WhoisSensorEntityDescription, ...] = ( 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 @@ SENSORS: tuple[WhoisSensorEntityDescription, ...] = ( 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 c29a476ca55..54eaf5a6917 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.const import ( 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 @@ class HassFlickAuth(AbstractFlickAuth): 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 @@ class HassFlickAuth(AbstractFlickAuth): ), ) - # 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 e8365f37411..de1942096b5 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 d6a1bd59d8e..92bc81b5aa0 100644 --- a/homeassistant/components/flick_electric/sensor.py +++ b/homeassistant/components/flick_electric/sensor.py @@ -15,8 +15,6 @@ from homeassistant.util.dt import utcnow 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 @@ class FlickPricingSensor(SensorEntity): 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 95121b5685b..9113d52f5c8 100644 --- a/homeassistant/components/flux_led/const.py +++ b/homeassistant/components/flux_led/const.py @@ -51,6 +51,7 @@ FLUX_LED_EXCEPTIONS: Final = ( 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 32b0d0ed9df..0f65c7c1797 100644 --- a/homeassistant/components/flux_led/discovery.py +++ b/homeassistant/components/flux_led/discovery.py @@ -38,7 +38,7 @@ from .const import ( 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 2a4d82de6d0..75d853c4001 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 @@ class HASensemeLight(SensemeEntity, LightEntity): 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 5c586d88fd5..8c9a7669889 100644 --- a/tests/components/senseme/__init__.py +++ b/tests/components/senseme/__init__.py @@ -12,32 +12,38 @@ MOCK_UUID = "77a6b7b3-925d-4695-a415-76d76dca4444" 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 = MagicMock(auto_spec=SensemeDevice) 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 _patch_discovery(device=None, no_device=None): 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 00000000000..21811452610 --- /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 d58541e708d..3fc113c6038 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 @@ class AugustData(AugustSubscriberMixin): 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 dfe9cc0f700..ae3fcdf90e3 100644 --- a/homeassistant/components/august/const.py +++ b/homeassistant/components/august/const.py @@ -4,7 +4,7 @@ from datetime import timedelta 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 dc05ee9b929..71a8b6c83aa 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 f7260088e0e..5dc97d4f7d8 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 08eaf12cf0c..e5c1a696fe8 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 4d73eec7f1f..320461ca6e9 100644 --- a/tests/components/august/test_init.py +++ b/tests/components/august/test_init.py @@ -30,6 +30,26 @@ from tests.components.august.mocks import ( ) +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 426114c6576..0ae80a22a47 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 00000000000..27eaef09b43 --- /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 74016d4492c..191e8fa97e9 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 1632c8ba9a3..e801d941a74 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 5dc97d4f7d8..65a6067fef7 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 e5c1a696fe8..bde0607e8b9 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 800db2cad79..d9ec64a02c9 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -49,6 +49,7 @@ DEFAULT_PAYLOAD_OFF = "OFF" 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 @@ class MqttBinarySensor(MqttEntity, BinarySensorEntity): 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 @@ class MqttBinarySensor(MqttEntity, BinarySensorEntity): 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 a13f0781dfb..1d94dc7ccf7 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 39e8d0d55b3..9ff389325ce 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -150,6 +150,7 @@ PLATFORMS = [ 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 c5c70ad33a4..4b4c6fb7af9 100644 --- a/homeassistant/components/mqtt/abbreviations.py +++ b/homeassistant/components/mqtt/abbreviations.py @@ -7,6 +7,7 @@ ABBREVIATIONS = { "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 @@ ABBREVIATIONS = { "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 4ccd81904b1..0feb21b0010 100644 --- a/homeassistant/components/mqtt/const.py +++ b/homeassistant/components/mqtt/const.py @@ -51,4 +51,7 @@ DOMAIN = "mqtt" 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 c9b0b816c4e..b31d90c76f8 100644 --- a/homeassistant/components/mqtt/discovery.py +++ b/homeassistant/components/mqtt/discovery.py @@ -50,6 +50,7 @@ SUPPORTED_COMPONENTS = [ "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 00000000000..e83aee26228 --- /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 00000000000..e500bdb6ea7 --- /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 | 60 ++++- 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, 353 insertions(+), 100 deletions(-) create mode 100644 homeassistant/components/plex/cast.py diff --git a/homeassistant/components/cast/__init__.py b/homeassistant/components/cast/__init__.py index 2623de0933e..cb631e17ccd 100644 --- a/homeassistant/components/cast/__init__.py +++ b/homeassistant/components/cast/__init__.py @@ -1,12 +1,21 @@ """Component to embed Google Cast.""" -import logging +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 6cca4cfa20b..0b7bc5e5748 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -11,7 +11,6 @@ from urllib.parse import quote 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 @@ from pychromecast.socket_client import ( ) 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 import ( ) 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 @@ from homeassistant.components.media_player.const import ( 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 @@ class CastDevice(MediaPlayerEntity): 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 @@ class CastDevice(MediaPlayerEntity): 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 @@ class CastDevice(MediaPlayerEntity): 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 @@ class CastDevice(MediaPlayerEntity): ) 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 00000000000..c2a09ff8810 --- /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 2d0114c0110..3b96f378906 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 5e14b5e7bd6..584f40016e0 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 @@ import pytest 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 import entity_registry as er 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 0b7bc5e5748..5f3381896da 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -498,7 +498,10 @@ class CastDevice(MediaPlayerEntity): """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 584f40016e0..bd22a558314 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 42849d4498d..e2e98e3fd5c 100644 --- a/homeassistant/components/tuya/fan.py +++ b/homeassistant/components/tuya/fan.py @@ -137,7 +137,7 @@ class TuyaFanEntity(TuyaEntity, FanEntity): [ { "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 @@ class TuyaFanEntity(TuyaEntity, FanEntity): 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 @@ class TuyaFanEntity(TuyaEntity, FanEntity): 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 a2612d07464..2cce88cbe36 100644 --- a/homeassistant/components/homewizard/coordinator.py +++ b/homeassistant/components/homewizard/coordinator.py @@ -8,6 +8,7 @@ import aiohwenergy 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 @@ class HWEnergyDeviceUpdateCoordinator(DataUpdateCoordinator[DeviceResponseEntry] """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 870b52446ba..0705da4938b 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 65a6067fef7..c43c8e4f475 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 bde0607e8b9..852128d2916 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 f862ca73e6c..b2c7ddf6be2 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 c43c8e4f475..2be34511a1c 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 852128d2916..465f352b115 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 0d235982462..47c5c45f2ad 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 2be34511a1c..1d28e4d7025 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 465f352b115..4242cc595b5 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 e83aee26228..6a268881593 100644 --- a/homeassistant/components/mqtt/siren.py +++ b/homeassistant/components/mqtt/siren.py @@ -161,7 +161,7 @@ class MqttSiren(MqttEntity, SirenEntity): 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 aadbed49980..9afdc4e02b8 100644 --- a/homeassistant/components/adguard/config_flow.py +++ b/homeassistant/components/adguard/config_flow.py @@ -80,8 +80,8 @@ class AdGuardHomeFlowHandler(ConfigFlow, domain=DOMAIN): 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 506ddfcfce0..cf1210b0884 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 1d28e4d7025..ef2142f9e66 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 4242cc595b5..8c6e6951817 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 d99f07a78ae..eb99b8519a9 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 ef2142f9e66..c89eb1598b8 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 8c6e6951817..e30a8be77e0 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 3fc113c6038..5d51017bfd6 100644 --- a/homeassistant/components/august/__init__.py +++ b/homeassistant/components/august/__init__.py @@ -206,12 +206,28 @@ class AugustData(AugustSubscriberMixin): 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 735b4b0a431..c5c11b7d3a9 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 c89eb1598b8..c8b82662ceb 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 e30a8be77e0..5ce35d69980 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 6df920f385c..3d0b25640b3 100644 --- a/homeassistant/components/whois/sensor.py +++ b/homeassistant/components/whois/sensor.py @@ -233,17 +233,20 @@ class WhoisSensorEntity(CoordinatorEntity, SensorEntity): 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 00000000000..c2820a7a13a --- /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 00000000000..d8782848c92 --- /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 fff4cfbf908..2f97d307b15 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 ac7cbe84459..3249705ce7c 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 c8b82662ceb..8bb86d47bb5 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 5ce35d69980..c5dabcbf94d 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 aa197061466..a1933ff9315 100644 --- a/homeassistant/components/pvoutput/config_flow.py +++ b/homeassistant/components/pvoutput/config_flow.py @@ -12,7 +12,7 @@ from homeassistant.core import HomeAssistant 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 @@ class PVOutputFlowHandler(ConfigFlow, domain=DOMAIN): 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 74d310d40ec..53768431a1d 100644 --- a/homeassistant/components/sonos/entity.py +++ b/homeassistant/components/sonos/entity.py @@ -9,6 +9,7 @@ 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 from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import DeviceInfo, Entity @@ -22,6 +23,8 @@ from .const import ( ) from .speaker import SonosSpeaker +SUB_FAIL_URL = "https://www.home-assistant.io/integrations/sonos/#network-requirements" + _LOGGER = logging.getLogger(__name__) @@ -70,10 +73,15 @@ class SonosEntity(Entity): 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 a63ed8b7e7b..c7f47333901 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 8bb86d47bb5..271bb4b583e 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 c5dabcbf94d..d880e4e65ce 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 fede7d96810..0b8bf55fcda 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 @@ class AlexaResponse: 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 2deca297f34..d14aaf5a68b 100644 --- a/homeassistant/components/intent_script/__init__.py +++ b/homeassistant/components/intent_script/__init__.py @@ -12,6 +12,7 @@ DOMAIN = "intent_script" CONF_INTENTS = "intents" CONF_SPEECH = "speech" +CONF_REPROMPT = "reprompt" CONF_ACTION = "action" CONF_CARD = "card" @@ -39,6 +40,10 @@ CONFIG_SCHEMA = vol.Schema( 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 @@ class ScriptIntentHandler(intent.IntentHandler): 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 @@ class ScriptIntentHandler(intent.IntentHandler): 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 ca154d20b75..13cc32a35b6 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -249,6 +249,7 @@ class IntentResponse: """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 @@ class IntentResponse: """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 d6c32996330..f15fa860c7b 100644 --- a/tests/components/alexa/test_intent.py +++ b/tests/components/alexa/test_intent.py @@ -13,6 +13,9 @@ from homeassistant.setup import async_setup_component 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 alexa_client(loop, hass, hass_client): "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 95b167caba6..6f345522e63 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 0ae80a22a47..7cca9c005ce 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 3ab87ad9d0e..d60a8aabb1a 100644 --- a/homeassistant/components/shelly/__init__.py +++ b/homeassistant/components/shelly/__init__.py @@ -80,6 +80,7 @@ BLOCK_PLATFORMS: Final = [ 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 00000000000..27773c629c0 --- /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 7cca9c005ce..049e6a37904 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 00000000000..4305ee4d7cb --- /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 25af3064d5d..e7d9bf11c32 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 3aadff93a01..09c42bf0547 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 8fe7d8e8c72..8c8b1e616c9 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 @@ BUTTON_ENTITIES: dict[str, HomeKitButtonEntityDescription] = { } -# 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 @@ class HomeKitEcobeeClearHoldButton(CharacteristicEntity, ButtonEntity): 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 4cb3f24bbc4..aa807f3e901 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 08b3e9823e3..e31296258e4 100644 --- a/homeassistant/components/homekit_controller/connection.py +++ b/homeassistant/components/homekit_controller/connection.py @@ -11,6 +11,7 @@ from aiohomekit.exceptions import ( 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 @@ class HKDevice: 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 9ec991c0d88..c27527d3638 100644 --- a/homeassistant/components/homekit_controller/const.py +++ b/homeassistant/components/homekit_controller/const.py @@ -51,33 +51,33 @@ HOMEKIT_ACCESSORY_DISPATCH = { } 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 @@ CHARACTERISTIC_PLATFORMS = { 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 ca24acc777a..bb6bab4a495 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 5bb7d634626..0ad64723478 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 bdf19b6d593..e5183b7be31 100644 --- a/homeassistant/components/homekit_controller/diagnostics.py +++ b/homeassistant/components/homekit_controller/diagnostics.py @@ -15,7 +15,7 @@ from .connection import HKDevice 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 89b13206e90..0b6f03b18e3 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 bde16abd23b..bb46c5b2dec 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 77d78074255..2e2d60750d8 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 fa8601e90ff..a3248e3fa02 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 cdae867ca85..fcb36d3c54a 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 6d87250f53c..dda763c4ae5 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 135124cb207..3cfe842e856 100644 --- a/homeassistant/components/homekit_controller/number.py +++ b/homeassistant/components/homekit_controller/number.py @@ -17,62 +17,62 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback 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 @@ class HomeKitEcobeeFanModeNumber(CharacteristicEntity, NumberEntity): 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 55c12c77820..82ae3aff691 100644 --- a/homeassistant/components/homekit_controller/select.py +++ b/homeassistant/components/homekit_controller/select.py @@ -35,7 +35,7 @@ class EcobeeModeSelect(CharacteristicEntity, SelectEntity): 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 @@ class EcobeeModeSelect(CharacteristicEntity, SelectEntity): """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 0cec354e1ab..13491c8ee03 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 @@ SIMPLE_SENSOR: dict[str, HomeKitSensorEntityDescription] = { 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 @@ SIMPLE_SENSOR: dict[str, HomeKitSensorEntityDescription] = { 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 @@ SIMPLE_SENSOR: dict[str, HomeKitSensorEntityDescription] = { ), } -# 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 8228d9546cb..2645907a962 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 271bb4b583e..f1b59e57f12 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 d880e4e65ce..94566810bcc 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 0891d6209b7..4fabe504d4b 100644 --- a/tests/components/homekit_controller/common.py +++ b/tests/components/homekit_controller/common.py @@ -10,7 +10,6 @@ from typing import Any, Final 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 131fed572f7..79dbc59a38a 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 bd9aa30f6ae..8d22f7ff4bc 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 a8a40bfa732..78bdb394f0c 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 6cc7f5336b4..55d5b168abe 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 fc1a48f0f5d..145d85eeed7 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 fbea04171cb..9fafa4afada 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 | 61 +++-- tests/components/sonarr/test_sensor.py | 39 +-- 7 files changed, 310 insertions(+), 359 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 8172cb4e0dd..cd3fb8f795a 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 00000000000..a03ae7532d4 --- /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 00000000000..e9ce88b233e --- /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 c3969df08fe..00000000000 --- 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 87b38e52742..52e7b9b61ca 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 @@ from homeassistant.data_entry_flow import ( 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 39d9b7fc24e..3a59f5d7cca 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.""" - with patch.object(hass.config_entries.flow, "async_init") as mock_flow_init: - entry = await setup_integration(hass, aioclient_mock, invalid_auth=True) + mock_sonarr.update.side_effect = SonarrAccessRestricted - assert entry.state is ConfigEntryState.SETUP_ERROR + with patch.object(hass.config_entries.flow, "async_init") as mock_flow_init: + 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_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 f68920e4e4f..8acf7d5b2c8 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.core import HomeAssistant 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 f4c50a24267..0ce00f1fe48 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 c0b0782f62e..f3f669de3b3 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 7d144619bc9..24229507877 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 1d8289b5ec0..053100d2e00 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 566917d7c39..8a9a40e3217 100644 --- a/tests/components/alexa/test_capabilities.py +++ b/tests/components/alexa/test_capabilities.py @@ -4,7 +4,6 @@ from unittest.mock import patch 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 . import ( 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 ea4589ddd42..c027fe8bc20 100644 --- a/homeassistant/components/guardian/config_flow.py +++ b/homeassistant/components/guardian/config_flow.py @@ -68,6 +68,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 b8d8a10752d..fc3157289e9 100644 --- a/tests/components/guardian/test_config_flow.py +++ b/tests/components/guardian/test_config_flow.py @@ -13,6 +13,8 @@ from homeassistant.components.guardian.config_flow import ( 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 5b5b20e7175..720a46b4659 100644 --- a/homeassistant/components/unifiprotect/config_flow.py +++ b/homeassistant/components/unifiprotect/config_flow.py @@ -36,7 +36,7 @@ from .const import ( 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 @@ class ProtectFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 45645d6f06b..559cfd37660 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 5fd1b7cc909..1bbe9fb435d 100644 --- a/tests/components/unifiprotect/__init__.py +++ b/tests/components/unifiprotect/__init__.py @@ -8,6 +8,7 @@ from unifi_discovery import AIOUnifiScanner, UnifiDevice, UnifiService 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 @@ UNIFI_DISCOVERY = UnifiDevice( 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 557eb3d5e79..fc5b2b32873 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 @@ from . import ( 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 0db85b12077..a0e0413366b 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.core import HomeAssistant 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 70485ac0c5f..c9eff204bf2 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -12,10 +12,7 @@ from typing import Any, TypedDict, cast from fritzconnection import FritzConnection from fritzconnection.core.exceptions import ( FritzActionError, - FritzActionFailedError, FritzConnectionException, - FritzInternalError, - FritzLookUpError, FritzSecurityError, FritzServiceError, ) @@ -46,6 +43,7 @@ from .const import ( DEFAULT_PORT, DEFAULT_USERNAME, DOMAIN, + FRITZ_EXCEPTIONS, SERVICE_CLEANUP, SERVICE_REBOOT, SERVICE_RECONNECT, @@ -188,9 +186,26 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator): _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 @@ class AvmWrapper(FritzBoxTools): "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 ae8ffe83e38..59200e07c78 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 @@ SWITCH_TYPE_PORTFORWARD = "PortForward" 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 6f99ab483e6..1dc60f4a59e 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 70b38c18708..212beddfae3 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 3a529c15cf3..346cc67d235 100644 --- a/homeassistant/components/hue/bridge.py +++ b/homeassistant/components/hue/bridge.py @@ -49,11 +49,12 @@ class HueBridge: 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 b9ffea7d3df..231c00e3d24 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 3dbfef42d16..f779fccdb3b 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 f1b59e57f12..46de83efe47 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 94566810bcc..e59308f3529 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 d1b43da9e27..fa3b8688c47 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 44486677384..15c1afc1b99 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 46de83efe47..2a88989c900 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 e59308f3529..b2832777f8a 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 049e6a37904..01906614e15 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 00000000000..3dd55933291 --- /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 01906614e15..9c1c8733ab4 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 00000000000..18d2325f38c --- /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 0cca8117080..74756efbf38 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 00000000000..e8968158682 --- /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 5f931933f9b..508b940a541 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 f895082e233..c1ca5b7717a 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 00000000000..ff1f06ff6f8 --- /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 b3b09105941..37713ec0634 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 6888f4ddf35..eb72ab1d10d 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 ebee9aee976..55d9377f8d2 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 da926cb5017..f6e364b044c 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 ebee9aee976..55d9377f8d2 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 cf4ccf81af6..4ad673ea7db 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 ebee9aee976..55d9377f8d2 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 c8b3c037c0e..9788f4904b6 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 00000000000..5bf7e7a4d1f --- /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 00000000000..65dabaa328a --- /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 0cae34cffeb..bda434b4439 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 b19184f1a33..e2e0aadeb5e 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 b239b4b143e..12cde31ea6d 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 ebee9aee976..55d9377f8d2 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 00000000000..7d004af7b54 --- /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 00000000000..d0c3523da94 --- /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 c14e2f83800..e2ed58741bd 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 00000000000..aec142c37cf --- /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 ffd5861f9cf..277046d209d 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 61c22e7e6ed..40d3a848254 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 07785249026..43f72f49867 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 00000000000..6cb6a0d4ea0 --- /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 7e155c6bdd7..4850982aede 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 4285f5fcbd0..4f0bc6628e3 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 ebee9aee976..55d9377f8d2 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 00000000000..8c6a8c07fdf --- /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 ba550fbbe2a..1edd6c06fc2 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 201cd7c02b5..0cde52b9297 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 095042b54b0..bfd24267091 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 25ad61ac70f..bca9ff03471 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 bb0bbef8760..a030b22455e 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 d0c3523da94..10bd4cb9a41 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 014783d7431..28375b45c2c 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 5dbfa18b18c..2f816f71c3c 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 793cd468f33..7850ff1c34d 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 55eb3a0d660..3b3e7dea4bf 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 5ff28250076..6cb73c4c9f2 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 00000000000..44e903b1220 --- /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 926f3cc4b9d..345ec01dce3 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 f77c82130a4..acf35c4c2a4 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 ebee9aee976..55d9377f8d2 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 f3555baadfe..2e792646cec 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 4393efb7859..979a8d50ee1 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 09b02ba8cb3..71b49b9ad2f 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 7e155c6bdd7..4850982aede 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 4efce28f37f..38331177895 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 4e1eaa0c695..adc8d7ef699 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 ba35bb73082..5472b660388 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 9b98369a859..41d3c402ef1 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 00000000000..6d9e58dc5de --- /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 a511aad9e42..b023b5d6011 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 61e2b50d7aa..c11b9874bae 100644 --- a/homeassistant/components/tradfri/__init__.py +++ b/homeassistant/components/tradfri/__init__.py @@ -15,9 +15,10 @@ import voluptuous as vol 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 async_setup_entry( 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 f96d1c09050..2a26391c43f 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 41fdd8c32d3..dd327acbf75 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 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.const import ( ATTR_EDITABLE, - ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE, CONF_ICON, @@ -29,7 +27,6 @@ from homeassistant.helpers import ( config_validation as cv, entity, entity_component, - event, service, storage, ) @@ -287,10 +284,7 @@ class Zone(entity.Entity): """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 @@ class Zone(entity.Entity): 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 @@ class Zone(entity.Entity): """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 @@ class Zone(entity.Entity): 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 54cb87aa772..8d0fddb921c 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 | 526 ++++++++++++++++++-- 5 files changed, 544 insertions(+), 79 deletions(-) diff --git a/.coveragerc b/.coveragerc index 9c1c8733ab4..a7baa17e852 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 0dfacb13e74..05bfb490cb8 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 do_authentication(hass, hass_config, config): 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 setup_services( 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 20cb13130ec..59de9b2cddf 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 01bd179e2ea..0ee257788dd 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 @@ from homeassistant.components.google import ( 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 d90efa29f6c..c3754511b04 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 + +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(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 +@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 -async def test_setup_component(hass, google_setup): - """Test setup component.""" - config = {"google": {CONF_CLIENT_ID: "id", CONF_CLIENT_SECRET: "secret"}} - - assert await async_setup_component(hass, "google", config) +@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_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 calendar_info == { - "cal_id": "qwertyuiopasdfghjklzxcvbnm@import.calendar.google.com", - "entities": [ - { - "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, +@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, + } + ], } - } - assert await async_setup_component(hass, "google", config) - assert hass.data[google.DATA_INDEX] == {} + ] + + +@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] + + +@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.""" + + 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] + ) + + +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( - "google", google.SERVICE_FOUND_CALENDARS, test_calendar, blocking=True + 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": {}, + }, ) - assert hass.data[google.DATA_INDEX].get(test_calendar["id"]) is not None + +@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), + ), + ( + { + "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( + DOMAIN, + SERVICE_ADD_EVENT, + { + "calendar_id": CALENDAR_ID, + "summary": "Summary", + "description": "Description", + **date_fields, + }, + blocking=True, + ) + mock_insert_event.assert_called() + + 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 1c3251d213c..c611fc1ee60 100644 --- a/homeassistant/components/unifi/unifi_entity_base.py +++ b/homeassistant/components/unifi/unifi_entity_base.py @@ -3,7 +3,7 @@ import logging 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 @@ class UniFiBase(Entity): 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 cf4861980a0..b490d43fffd 100644 --- a/tests/components/unifi/test_device_tracker.py +++ b/tests/components/unifi/test_device_tracker.py @@ -18,7 +18,7 @@ from homeassistant.components.unifi.const import ( 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 e31296258e4..7b5f7114170 100644 --- a/homeassistant/components/homekit_controller/connection.py +++ b/homeassistant/components/homekit_controller/connection.py @@ -11,7 +11,6 @@ from aiohomekit.exceptions import ( 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 @@ class HKDevice: 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 cb631e17ccd..86e5557160c 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 5f3381896da..975fa3f5836 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -463,7 +463,7 @@ class CastDevice(MediaPlayerEntity): 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 c2a09ff8810..59f23a681f8 100644 --- a/homeassistant/components/plex/cast.py +++ b/homeassistant/components/plex/cast.py @@ -14,7 +14,9 @@ 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]: +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 0f231fa9303..ed65a83a1e7 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -48,8 +48,6 @@ class HomeKitEntity(Entity): self._features = 0 self.setup() - self._signals = [] - super().__init__() @property @@ -71,7 +69,7 @@ class HomeKitEntity(Entity): 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 @@ class HomeKitEntity(Entity): 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 @@ class HomeKitEntity(Entity): 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 18f19265f59..6a2200b69eb 100644 --- a/homeassistant/components/homekit_controller/config_flow.py +++ b/homeassistant/components/homekit_controller/config_flow.py @@ -280,9 +280,13 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 0ad64723478..a69d189ebd5 100644 --- a/homeassistant/components/homekit_controller/device_trigger.py +++ b/homeassistant/components/homekit_controller/device_trigger.py @@ -76,7 +76,7 @@ class TriggerSource: 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 e5183b7be31..fd404636a22 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 3cfe842e856..9c28bc8ebdd 100644 --- a/homeassistant/components/homekit_controller/number.py +++ b/homeassistant/components/homekit_controller/number.py @@ -122,7 +122,7 @@ class HomeKitNumber(CharacteristicEntity, NumberEntity): 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 2645907a962..c681ca4d288 100644 --- a/homeassistant/components/homekit_controller/switch.py +++ b/homeassistant/components/homekit_controller/switch.py @@ -143,7 +143,7 @@ class DeclarativeCharacteristicSwitch(CharacteristicEntity, SwitchEntity): 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 f41ebd8b1ce..7e35a4929f8 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 06c1353ce73..bb1cc2a6679 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -35,7 +35,6 @@ IGNORED_MODULES: Final[list[str]] = [ "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 627d0df767d..b99cc7b86e1 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 7041b22ebe6..619e85cbdd4 100644 --- a/homeassistant/components/tuya/switch.py +++ b/homeassistant/components/tuya/switch.py @@ -501,6 +501,12 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { 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 e47dc67d58d..bedcc5f821c 100644 --- a/homeassistant/components/dnsip/config_flow.py +++ b/homeassistant/components/dnsip/config_flow.py @@ -110,11 +110,13 @@ class DnsIPConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 9057f3e8c33..7dfc3aaa544 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 3ebbdfe91da..59dcb81aa94 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 05bfb490cb8..9f85d41774b 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 @@ ADD_EVENT_SERVICE_SCHEMA = vol.Schema( ) -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 setup_services( 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 6db9c810ad9..90b5bbb3d89 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 @@ class GoogleCalendarEventDevice(CalendarEventDevice): 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 @@ class GoogleCalendarData: 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 @@ class GoogleCalendarData: 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 @@ class GoogleCalendarData: 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 84a6d0771e6..0361f4562c4 100644 --- a/homeassistant/components/motioneye/config_flow.py +++ b/homeassistant/components/motioneye/config_flow.py @@ -222,17 +222,15 @@ class MotionEyeOptionsFlow(OptionsFlow): 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 57def069d59..9ef0f78874d 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 1395478b6e9..039ff34c9f7 100644 --- a/homeassistant/components/tradfri/coordinator.py +++ b/homeassistant/components/tradfri/coordinator.py @@ -51,27 +51,23 @@ class TradfriDeviceDataUpdateCoordinator(DataUpdateCoordinator[Device]): @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 @@ class TradfriDeviceDataUpdateCoordinator(DataUpdateCoordinator[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 @@ class TradfriDeviceDataUpdateCoordinator(DataUpdateCoordinator[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 57ce0125c93..f0e18953402 100644 --- a/homeassistant/components/honeywell/climate.py +++ b/homeassistant/components/honeywell/climate.py @@ -242,7 +242,7 @@ class HoneywellUSThermostat(ClimateEntity): # 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 01e22297c0d..f1a2d9aab84 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,7 +51,18 @@ class EditAutomationConfigView(EditIdBasedConfigView): def _write_value(self, hass, data, config_key, new_value): """Set value.""" - index = None + updated_value = {CONF_ID: config_key} + + # Iterate through some keys that we want to have ordered in the output + 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(new_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. @@ -60,23 +70,8 @@ class EditAutomationConfigView(EditIdBasedConfigView): 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) + data[index] = updated_value + updated = True - # 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] - 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 + if not updated: + data.append(updated_value) diff --git a/homeassistant/components/config/scene.py b/homeassistant/components/config/scene.py index 41b8dce0957..6523ff84158 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 80ee38350aa..6f782fdbbff 100644 --- a/tests/components/config/test_automation.py +++ b/tests/components/config/test_automation.py @@ -80,6 +80,43 @@ async def test_update_device_config(hass, hass_client, setup_automation): 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 4f27ff4ff4f..ed059856881 100644 --- a/homeassistant/components/unifi/diagnostics.py +++ b/homeassistant/components/unifi/diagnostics.py @@ -7,14 +7,14 @@ 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, 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 3de9393e5b9..ccec7fcb48a 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 c9eff204bf2..dede4b82c02 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -372,13 +372,14 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator): 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 1eef7aff083..49e49ac2efa 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 15c1afc1b99..30086a676e4 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 2a88989c900..8f5ca63a3ec 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 b2832777f8a..c25db3f6345 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 ed65a83a1e7..974cb5e1dfc 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -17,7 +17,7 @@ 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 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 @@ from .storage import EntityMapStorage _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 @@ class HomeKitEntity(Entity): """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 @@ class HomeKitEntity(Entity): 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 @@ class HomeKitEntity(Entity): 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 @@ class HomeKitEntity(Entity): 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 @@ class HomeKitEntity(Entity): """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 e7d9bf11c32..0194036db4e 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 @@ async def async_setup_entry( 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 @@ class HomeKitAlarmControlPanelEntity(HomeKitEntity, AlarmControlPanelEntity): ] @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 @@ class HomeKitAlarmControlPanelEntity(HomeKitEntity, AlarmControlPanelEntity): """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 09c42bf0547..91c1c47d47e 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 8c8b1e616c9..7d2c737b509 100644 --- a/homeassistant/components/homekit_controller/button.py +++ b/homeassistant/components/homekit_controller/button.py @@ -19,8 +19,10 @@ 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 +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 @@ class HomeKitButton(CharacteristicEntity, ButtonEntity): """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 63b06cce338..0ffa0a22f4d 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 aa807f3e901..e8179b0bc6b 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 @@ from aiohomekit.model.characteristics import ( 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 @@ async def async_setup_entry( 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 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): 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 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): 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 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): ) @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 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): 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 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): 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 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): 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 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): 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 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): 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 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): ): 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 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): return modes @property - def swing_mode(self): + def swing_mode(self) -> str: """Return the swing setting. Requires SUPPORT_SWING_MODE. @@ -287,7 +290,7 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): 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 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): ) @property - def supported_features(self): + def supported_features(self) -> int: """Return the list of supported features.""" features = 0 @@ -321,7 +324,7 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): return features @property - def temperature_unit(self): + def temperature_unit(self) -> str: """Return the unit of measurement.""" return TEMP_CELSIUS @@ -329,7 +332,7 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): 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 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): 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 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): 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 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): 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 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): ) @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 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): 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 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): 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 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): 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 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): 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 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): 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 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): 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 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): 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 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): 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 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): 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 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): 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 6a2200b69eb..54d265a5f4a 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 @@ INSECURE_CODES = { } -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 7b5f7114170..8bd5351906c 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 @@ from aiohomekit.exceptions import ( 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 @@ MAX_POLL_FAILURES_TO_DECLARE_UNAVAILABLE = 3 _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 @@ class HKDevice: 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 @@ class HKDevice: # 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 @@ class HKDevice: # 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 @@ class HKDevice: 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 @@ class HKDevice: 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 @@ class HKDevice: (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 @@ class HKDevice: ) @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 @@ class HKDevice: 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 @@ class HKDevice: 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 @@ class HKDevice: 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 @@ class HKDevice: 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 @@ class HKDevice: 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 @@ class HKDevice: 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 @@ class HKDevice: _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 @@ class HKDevice: 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 @@ class HKDevice: 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 bb6bab4a495..a1aa8dbd807 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 @@ async def async_setup_entry( 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 @@ class HomeKitGarageDoorCover(HomeKitEntity, CoverEntity): ] @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 @@ class HomeKitGarageDoorCover(HomeKitEntity, CoverEntity): 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 @@ class HomeKitWindowCover(HomeKitEntity, CoverEntity): ] @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 @@ class HomeKitWindowCover(HomeKitEntity, CoverEntity): 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 @@ class HomeKitWindowCover(HomeKitEntity, CoverEntity): ) 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 @@ class HomeKitWindowCover(HomeKitEntity, CoverEntity): ) @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 0b6f03b18e3..71d71ce469f 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 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity): ] @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 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity): 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 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity): 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 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity): {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 @@ class BaseHomeKitFan(HomeKitEntity, FanEntity): 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 bb46c5b2dec..bc922dd4ec1 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 @@ class HomeKitHumidifier(HomeKitEntity, HumidifierEntity): ] @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 @@ class HomeKitDehumidifier(HomeKitEntity, HumidifierEntity): ] @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 2e2d60750d8..2b82f27022b 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 @@ async def async_setup_entry( 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 @@ class HomeKitLight(HomeKitEntity, LightEntity): ] @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 @@ class HomeKitLight(HomeKitEntity, LightEntity): ) @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 @@ class HomeKitLight(HomeKitEntity, LightEntity): 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 @@ class HomeKitLight(HomeKitEntity, LightEntity): 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 a3248e3fa02..248bb93a68f 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 @@ async def async_setup_entry( 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 @@ class HomeKitLock(HomeKitEntity, LockEntity): ] @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 @@ class HomeKitLock(HomeKitEntity, LockEntity): 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 @@ class HomeKitLock(HomeKitEntity, LockEntity): ) @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 @@ class HomeKitLock(HomeKitEntity, LockEntity): ) @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 dda763c4ae5..7318343b643 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 @@ from aiohomekit.model.characteristics import ( 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 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity): ] @property - def supported_features(self): + def supported_features(self) -> int: """Flag media player features that are supported.""" features = 0 @@ -108,10 +110,10 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity): 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 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity): ) @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 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity): 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 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity): 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 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity): 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 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity): {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 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity): {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 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity): {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 9c28bc8ebdd..a473e30fe6d 100644 --- a/homeassistant/components/homekit_controller/number.py +++ b/homeassistant/components/homekit_controller/number.py @@ -13,8 +13,10 @@ 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 +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 @@ class HomeKitNumber(CharacteristicEntity, NumberEntity): 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 @@ class HomeKitNumber(CharacteristicEntity, NumberEntity): """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 @@ class HomeKitNumber(CharacteristicEntity, NumberEntity): 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 @@ class HomeKitEcobeeFanModeNumber(CharacteristicEntity, NumberEntity): """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 82ae3aff691..681f24b9ab8 100644 --- a/homeassistant/components/homekit_controller/select.py +++ b/homeassistant/components/homekit_controller/select.py @@ -32,7 +32,7 @@ class EcobeeModeSelect(CharacteristicEntity, SelectEntity): 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 13491c8ee03..b7d7b8005ed 100644 --- a/homeassistant/components/homekit_controller/sensor.py +++ b/homeassistant/components/homekit_controller/sensor.py @@ -5,7 +5,7 @@ from collections.abc import Callable 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.const import ( ) 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 @@ class HomeKitBatterySensor(HomeKitEntity, SensorEntity): ] @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 @@ class HomeKitBatterySensor(HomeKitEntity, SensorEntity): 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 @@ class HomeKitBatterySensor(HomeKitEntity, SensorEntity): 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 @@ class SimpleSensor(CharacteristicEntity, SensorEntity): 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 @@ async def async_setup_entry( 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 4d512fbbc5d..fc6970f078a 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_STORAGE_VERSION = 1 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 @@ class EntityMapStorage: 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 c681ca4d288..07d0e21e59f 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 @@ from aiohomekit.model.characteristics import ( 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 @@ SWITCH_ENTITIES: dict[str, DeclarativeSwitchEntityDescription] = { 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 @@ class HomeKitValve(HomeKitEntity, SwitchEntity): 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 @@ class HomeKitValve(HomeKitEntity, SwitchEntity): 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 @@ class DeclarativeCharacteristicSwitch(CharacteristicEntity, SwitchEntity): 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 @@ async def async_setup_entry( 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 7c600f6b442..41ae27b2992 100644 --- a/homeassistant/components/netatmo/sensor.py +++ b/homeassistant/components/netatmo/sensor.py @@ -839,15 +839,16 @@ class NetatmoPublicSensor(NetatmoBase, SensorEntity): 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 8abab0e0225..b62975a9746 100644 --- a/homeassistant/components/apple_tv/media_player.py +++ b/homeassistant/components/apple_tv/media_player.py @@ -162,7 +162,10 @@ class AppleTvMediaPlayer(AppleTVEntity, MediaPlayerEntity): 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 deb65c1f0a8..bd511d84eb5 100644 --- a/homeassistant/components/apple_tv/__init__.py +++ b/homeassistant/components/apple_tv/__init__.py @@ -179,7 +179,6 @@ class AppleTVManager: 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 @@ class AppleTVManager: 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 b62975a9746..1f8cabb1d14 100644 --- a/homeassistant/components/apple_tv/media_player.py +++ b/homeassistant/components/apple_tv/media_player.py @@ -171,9 +171,6 @@ class AppleTvMediaPlayer(AppleTVEntity, MediaPlayerEntity): @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 75d853c4001..3036dc1d04d 100644 --- a/homeassistant/components/senseme/light.py +++ b/homeassistant/components/senseme/light.py @@ -51,7 +51,8 @@ class HASensemeLight(SensemeEntity, LightEntity): 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 21811452610..c585cfc31bf 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 ae414f99e55..a67c1ab76e1 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 d6355fd3809..9a04764873c 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 343acd4b5d9..06c91921d40 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 73875ea0423..5dd3cb32bf4 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 00000000000..7b8bda72fff --- /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 a5f57e2f576..17f42da5805 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 78cbc37a7d9..58ba93cd276 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 00eaf2929dd..a96698dedac 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 97a827f3d6b..35de7e89ed1 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 b5c2d1f18f7..0f69f0e96c9 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 00000000000..d99562320c8 --- /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 f59a6334403..f9cf06ceaa6 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 f703f991f6f..206d8b51233 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 8f5ca63a3ec..2f2e7d9347d 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 c25db3f6345..34101966b2b 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 707449e4002..421a0f7ed6a 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 bbeaceb08cd..abb0696360b 100644 --- a/homeassistant/components/sonos/const.py +++ b/homeassistant/components/sonos/const.py @@ -160,6 +160,7 @@ 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_LINEIN = "Line-in" SOURCE_TV = "TV" diff --git a/homeassistant/components/sonos/speaker.py b/homeassistant/components/sonos/speaker.py index 5f06fe976ac..7777265a124 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -12,6 +12,7 @@ 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.events_base import Event as SonosEvent, SubscriptionBase @@ -56,6 +57,7 @@ from .const import ( SONOS_STATE_PLAYING, SONOS_STATE_TRANSITIONING, SONOS_STATE_UPDATED, + SONOS_VANISHED, SOURCE_LINEIN, SOURCE_TV, SUBSCRIPTION_TIMEOUT, @@ -225,6 +227,7 @@ class SonosSpeaker: (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 @@ class SonosSpeaker: 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 @@ class SonosSpeaker: 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 @@ class SonosSpeaker: @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 ed568aa8a46..e28b2adff42 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 2f2e7d9347d..c8a4924566d 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 a150a0cf49c..ff9e70cc9c0 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -24,7 +24,11 @@ from homeassistant.const import ( 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 @@ class MqttDiscoveryUpdate(Entity): 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 @@ class MqttEntityDeviceInfo(Entity): 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 7d3ea839ebd..ec0162af915 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 d967450788f..45ae6f90e6d 100644 --- a/homeassistant/components/aussie_broadband/__init__.py +++ b/homeassistant/components/aussie_broadband/__init__.py @@ -70,7 +70,7 @@ async def async_setup_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: """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 a2eceff6870..3a101753445 100644 --- a/homeassistant/components/axis/device.py +++ b/homeassistant/components/axis/device.py @@ -13,6 +13,7 @@ from axis.streammanager import SIGNAL_PLAYING, STATE_STOPPED 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 @@ class AxisNetworkDevice: 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 ee2f51303f2..48a639e56f6 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 8fa0c6133df..d197f27910b 100644 --- a/homeassistant/components/deconz/gateway.py +++ b/homeassistant/components/deconz/gateway.py @@ -4,9 +4,9 @@ import asyncio 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 @@ class DeconzGateway: 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 a6678d8b533..27594703fd2 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 8db69b38927..7b5ae85bdff 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 1018d23fd8f..06264153af2 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 e1b21efe541..a68f56e2965 100644 --- a/homeassistant/components/forked_daapd/media_player.py +++ b/homeassistant/components/forked_daapd/media_player.py @@ -115,7 +115,7 @@ async def async_setup_entry( ] = 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 327b6314a34..ec0085c9d32 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 22f6ef2ed1d..6272015e73c 100644 --- a/homeassistant/components/glances/__init__.py +++ b/homeassistant/components/glances/__init__.py @@ -163,7 +163,7 @@ class GlancesData: ) @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 4ec610f1f75..4e109ae95a7 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 843cc6ea642..4cd0940adb7 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 68a005c5f72..bafd4c470db 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 5aaa243282b..c88e26e1c90 100644 --- a/homeassistant/components/islamic_prayer_times/__init__.py +++ b/homeassistant/components/islamic_prayer_times/__init__.py @@ -201,7 +201,7 @@ class IslamicPrayerClient: ) @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 9250b567d1b..b66e6d4676e 100644 --- a/homeassistant/components/isy994/__init__.py +++ b/homeassistant/components/isy994/__init__.py @@ -227,7 +227,7 @@ async def async_setup_entry( 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 8a494b88d91..ecd9ece1bd0 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 f018d1a5133..98440766334 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 2f47aee9c02..bbc3b16875d 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 e018ef94f7d..91fd353f2e0 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 6be66fe64c1..775c512f946 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 ba934fefa12..1ea24ae9709 100644 --- a/homeassistant/components/opentherm_gw/__init__.py +++ b/homeassistant/components/opentherm_gw/__init__.py @@ -83,7 +83,7 @@ CONFIG_SCHEMA = vol.Schema( 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 183eb98e213..19c93533867 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 41b7f0d0afd..26a158d240f 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 af4472ac7ae..67f1895943d 100644 --- a/homeassistant/components/plugwise/gateway.py +++ b/homeassistant/components/plugwise/gateway.py @@ -145,7 +145,7 @@ async def async_setup_entry_gw(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.""" 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 2e37499b10f..362fd615700 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 dc16e92b237..0a58effa481 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 b5f16574f1a..8580ce8f8fc 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 fd036daf385..a7fb40eafd4 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 f89ad5feedc..42fc806ab54 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 742b0a34b17..c4c7c551375 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 6b9d8c0aeb1..a824185b13e 100644 --- a/homeassistant/components/transmission/__init__.py +++ b/homeassistant/components/transmission/__init__.py @@ -330,7 +330,7 @@ class TransmissionClient: ) @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 5ee2023ce93..be59a25f69f 100644 --- a/homeassistant/components/unifi/controller.py +++ b/homeassistant/components/unifi/controller.py @@ -26,6 +26,7 @@ from aiounifi.events import ( 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 @@ from homeassistant.const import ( 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 @@ class UniFiController: 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 2e708a8b206..ef293c279be 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 ab5bda8dd1d..f2d11d53862 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 d1b8e9d4f46..4d226f233b2 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 | 44 +-- homeassistant/components/sensibo/climate.py | 365 ++++++++++-------- .../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, 325 insertions(+), 223 deletions(-) create mode 100644 homeassistant/components/sensibo/coordinator.py diff --git a/.coveragerc b/.coveragerc index a7baa17e852..b592f1c416d 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 7401a8c2150..b62482b60b5 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 648e05dbe79..b711bca5ac3 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 @@ from homeassistant.const import ( 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 @@ SENSIBO_TO_HA = { "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_setup_entry( ) -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, - ) - 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 - ) - self._attr_temperature_unit = self._temperature_unit - - self._attr_supported_features = 0 - for key in self._ac_states: + 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: - self._attr_supported_features |= FIELD_TO_FLAG[key] + features |= FIELD_TO_FLAG[key] + return features - self._attr_state = self._external_state or super().state + @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 + ) + + @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, + ) + + @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 77f1049d8d2..8544972baee 100644 --- a/homeassistant/components/sensibo/config_flow.py +++ b/homeassistant/components/sensibo/config_flow.py @@ -16,7 +16,7 @@ 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 _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 fb387e64a1a..e6d5bebff42 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 00000000000..4781250874f --- /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 586ad3b4168..6438ea37e18 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 2543ef580a9..e935cf004be 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 c8a4924566d..6441d7421cc 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 34101966b2b..88c456aa8e3 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 1e26a9c46d4..887c0517d05 100644 --- a/homeassistant/components/config/config_entries.py +++ b/homeassistant/components/config/config_entries.py @@ -3,6 +3,7 @@ from __future__ import annotations from http import HTTPStatus +from aiohttp import web import aiohttp.web_exceptions import voluptuous as vol @@ -11,7 +12,7 @@ from homeassistant.auth.permissions.const import CAT_CONFIG_ENTRIES, POLICY_EDIT 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 @@ class ConfigManagerFlowIndexView(FlowManagerIndexView): 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 ff8fb295cd5..052d3de4768 100644 --- a/homeassistant/exceptions.py +++ b/homeassistant/exceptions.py @@ -199,3 +199,15 @@ class RequiredParameterMissing(HomeAssistantError): ), ) 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 5ff6519f6ec..5c56cb55b19 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -18,7 +18,7 @@ from .const import ( 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 7f88d9b482b..6608bf3471d 100644 --- a/tests/components/config/test_config_entries.py +++ b/tests/components/config/test_config_entries.py @@ -252,6 +252,35 @@ async def test_initialize_flow(hass, client): } +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 49e49ac2efa..4f1e6eff032 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 30086a676e4..438fa0eba8d 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 6441d7421cc..545a758effa 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 88c456aa8e3..e542e233324 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 d9ec64a02c9..c500d52dd70 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -20,6 +20,8 @@ from homeassistant.const import ( 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 @@ 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 @@ -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 @@ class MqttBinarySensor(MqttEntity, BinarySensorEntity): 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 ff9e70cc9c0..f49af86360d 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -524,6 +524,11 @@ class MqttDiscoveryUpdate(Entity): 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 c457cff5d09..59f124155d3 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -23,12 +23,15 @@ from homeassistant.const import ( 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 @@ class MqttSensor(MqttEntity, SensorEntity): 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 1d94dc7ccf7..39685db2afc 100644 --- a/tests/components/mqtt/test_binary_sensor.py +++ b/tests/components/mqtt/test_binary_sensor.py @@ -36,6 +36,7 @@ from .test_common import ( 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 @@ from .test_common import ( 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 ba2c9e3871a..593d08f4c87 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 a511938f0d1..ad43a7b2353 100644 --- a/tests/components/mqtt/test_sensor.py +++ b/tests/components/mqtt/test_sensor.py @@ -43,6 +43,7 @@ from .test_common import ( 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 @@ from .test_common import ( 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 720a46b4659..0890a962e5b 100644 --- a/homeassistant/components/unifiprotect/config_flow.py +++ b/homeassistant/components/unifiprotect/config_flow.py @@ -90,7 +90,11 @@ class ProtectFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 fc5b2b32873..a1609984be3 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 ada9c9fb346..46a0ac81d90 100644 --- a/homeassistant/components/iotawatt/coordinator.py +++ b/homeassistant/components/iotawatt/coordinator.py @@ -51,6 +51,7 @@ class IotawattUpdater(DataUpdateCoordinator): 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 62f65741566..acc4577fa8a 100644 --- a/homeassistant/components/iotawatt/sensor.py +++ b/homeassistant/components/iotawatt/sensor.py @@ -8,6 +8,7 @@ import logging from iotawattpy.sensor import Sensor from homeassistant.components.sensor import ( + ATTR_LAST_RESET, SensorDeviceClass, SensorEntity, SensorEntityDescription, @@ -219,6 +220,9 @@ class IotaWattSensor(update_coordinator.CoordinatorEntity, SensorEntity): 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 229a8fef366..8f9526b6800 100644 --- a/homeassistant/util/async_.py +++ b/homeassistant/util/async_.py @@ -88,7 +88,7 @@ def run_callback_threadsafe( 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 d272da8fe96..f02d3c03b4b 100644 --- a/tests/util/test_async.py +++ b/tests/util/test_async.py @@ -5,6 +5,7 @@ from unittest.mock import MagicMock, Mock, patch 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 e462d76fa31..3e32d301b70 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 593d08f4c87..78c37b1105a 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 a71c332b70b..9101b895218 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -3,7 +3,7 @@ import asyncio 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 7353ba6fd21..07c2898f204 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.core import HomeAssistant, ServiceCall, callback 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 @@ DEFAULT_MODE = MODE_PROD 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 async_setup(hass: HomeAssistant, config: ConfigType) -> bool: 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 4a513aff117..78a8f83eef6 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 fd0b76a5c8a..3be3d67e32b 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 32014be7774..a5d0ca736fc 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 6fc73f40096..bdef681fdd2 100644 --- a/homeassistant/components/shodan/sensor.py +++ b/homeassistant/components/shodan/sensor.py @@ -67,7 +67,7 @@ class ShodanSensor(SensorEntity): 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 00000000000..fff5e8fa565 --- /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 e2a1fdbc9f3..85df150d663 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 952b27937df..bdda51d4093 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 00000000000..1f4fd90ff08 --- /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 d1f87c83cc8..d5124e40856 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 dee7d514b48..de5ce971fa0 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 7b82a2f08f7..6cb3d42656e 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 00000000000..0e97b8c1265 --- /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 a69b353354b..78655f3ac42 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 7e5fe76d3af..efb55133c9c 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 00000000000..7efa4d7ff45 --- /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 00000000000..df9322bf01d --- /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 00000000000..0405051cead --- /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 00000000000..16aa28fb6b2 --- /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 dad07f9b771..2951e71b114 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 0bce46abd97..bb19124b55c 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 9c3129a2c6c..1e96b5fee0d 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 00000000000..a461c335f55 --- /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 00000000000..95968bce45c --- /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 00000000000..e3a46094da9 --- /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 8ce6a8fb855..57cc475b383 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 deb60db2004..17c63089dbf 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 00000000000..127177e84b4 --- /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 03b49267d12..561c9efa24b 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 023c867ee9b..691c4e2a350 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 12acea8a7df..c5bc63ee29a 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 00000000000..57c70ac4094 --- /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 00000000000..1c904302541 --- /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 c506f9f6aac..e22b8feebd1 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 00000000000..3dff7685122 --- /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 9bae4cf9827..9745cd934ed 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 694cb41cf44..5c687ceb5b6 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 b0cadc8c8fd..54900940d9b 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 a0e57d47083..5ac91833471 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 364e390ddc2..16e7f3b5d17 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 bc8212e318b..0e4c6baa287 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 99414ed3b6f..3cfce894ca2 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 238ff1db87d..4ef26a11130 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 7e799a83ee2..ca6eca9ea9f 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 2f186af2ffe..774cd05fd9f 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 a8cdb853749..a133ec6c2dc 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -820,17 +820,6 @@ class SimpliSafeEntity(CoordinatorEntity): ): 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 @@ class SimpliSafeEntity(CoordinatorEntity): } ) + # 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 c97cc44aea3..00726f6db38 100644 --- a/homeassistant/components/accuweather/weather.py +++ b/homeassistant/components/accuweather/weather.py @@ -62,6 +62,9 @@ class AccuWeatherEntity(CoordinatorEntity, WeatherEntity): """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 6c1bc76e9b1..02ace5d3f1d 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 c5c11b7d3a9..37e0ae485c6 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 545a758effa..f8d4c19220b 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 e542e233324..74c0be71d69 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 63c4a79b96f..eaea908e358 100644 --- a/homeassistant/components/mqtt/alarm_control_panel.py +++ b/homeassistant/components/mqtt/alarm_control_panel.py @@ -173,7 +173,7 @@ class MqttAlarm(MqttEntity, alarm.AlarmControlPanelEntity): 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 @@ class MqttAlarm(MqttEntity, alarm.AlarmControlPanelEntity): 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 @@ class MqttAlarm(MqttEntity, alarm.AlarmControlPanelEntity): }, ) + 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 c500d52dd70..b84ddaad404 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -164,7 +164,7 @@ class MqttBinarySensor(MqttEntity, BinarySensorEntity, RestoreEntity): 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 @@ class MqttBinarySensor(MqttEntity, BinarySensorEntity, RestoreEntity): 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 @@ class MqttBinarySensor(MqttEntity, BinarySensorEntity, RestoreEntity): }, ) + 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 7143b65ed9e..993251606ed 100644 --- a/homeassistant/components/mqtt/button.py +++ b/homeassistant/components/mqtt/button.py @@ -95,6 +95,9 @@ class MqttButton(MqttEntity, ButtonEntity): 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 5c2b8258f01..1c060f7f32a 100644 --- a/homeassistant/components/mqtt/camera.py +++ b/homeassistant/components/mqtt/camera.py @@ -90,7 +90,7 @@ class MqttCamera(MqttEntity, Camera): """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 @@ class MqttCamera(MqttEntity, Camera): """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 @@ class MqttCamera(MqttEntity, Camera): }, ) + 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 2e19a345bc3..02d4f267fe8 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -358,11 +358,6 @@ class MqttClimate(MqttEntity, ClimateEntity): """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 @@ class MqttClimate(MqttEntity, ClimateEntity): 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 @@ class MqttClimate(MqttEntity, ClimateEntity): 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 95ea6182bf1..dfb48fb89e2 100644 --- a/homeassistant/components/mqtt/cover.py +++ b/homeassistant/components/mqtt/cover.py @@ -335,7 +335,7 @@ class MqttCover(MqttEntity, CoverEntity): 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 @@ class MqttCover(MqttEntity, CoverEntity): "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 3ee5f22be90..a7b597d0689 100644 --- a/homeassistant/components/mqtt/device_tracker/schema_discovery.py +++ b/homeassistant/components/mqtt/device_tracker/schema_discovery.py @@ -77,7 +77,7 @@ class MqttDeviceTracker(MqttEntity, TrackerEntity): 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 @@ class MqttDeviceTracker(MqttEntity, TrackerEntity): 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 @@ class MqttDeviceTracker(MqttEntity, TrackerEntity): }, ) + 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 fb6d21c8538..f6d60ae8cbe 100644 --- a/homeassistant/components/mqtt/fan.py +++ b/homeassistant/components/mqtt/fan.py @@ -351,7 +351,7 @@ class MqttFan(MqttEntity, FanEntity): 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 @@ class MqttFan(MqttEntity, FanEntity): } 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 df1b7667ef7..b2c4ed4b916 100644 --- a/homeassistant/components/mqtt/humidifier.py +++ b/homeassistant/components/mqtt/humidifier.py @@ -267,7 +267,7 @@ class MqttHumidifier(MqttEntity, HumidifierEntity): 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 @@ class MqttHumidifier(MqttEntity, HumidifierEntity): } 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 4f692c8063c..f164abe5297 100644 --- a/homeassistant/components/mqtt/light/schema_basic.py +++ b/homeassistant/components/mqtt/light/schema_basic.py @@ -417,12 +417,10 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): """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 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): "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 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): "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 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): 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 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): 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 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): 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 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): 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 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): 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 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): 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 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): 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 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): _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 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): 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 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): 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 3adaec38adb..2d9b8f6a388 100644 --- a/homeassistant/components/mqtt/light/schema_json.py +++ b/homeassistant/components/mqtt/light/schema_json.py @@ -304,9 +304,8 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): 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 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): 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 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): }, ) + 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 54252ebc0b0..736ff98f321 100644 --- a/homeassistant/components/mqtt/light/schema_template.py +++ b/homeassistant/components/mqtt/light/schema_template.py @@ -156,14 +156,12 @@ class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity): 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 @@ class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity): 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 @@ class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity): }, ) + 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 1c280405522..89917f4cc5c 100644 --- a/homeassistant/components/mqtt/lock.py +++ b/homeassistant/components/mqtt/lock.py @@ -123,7 +123,7 @@ class MqttLock(MqttEntity, LockEntity): 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 @@ class MqttLock(MqttEntity, LockEntity): # 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 @@ class MqttLock(MqttEntity, LockEntity): }, ) + 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 f49af86360d..1f4bbe9d949 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -66,7 +66,11 @@ from .discovery import ( 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 @@ class MqttAttributes(Entity): 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() + 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.""" - self._attributes_config = config 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 @@ class MqttAttributes(Entity): _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 @@ class MqttAttributes(Entity): }, ) + 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 @@ class MqttAvailability(Entity): 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 @@ class MqttAvailability(Entity): ) ) - 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 @@ class MqttAvailability(Entity): 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 @@ class MqttAvailability(Entity): 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 @@ class MqttAvailability(Entity): 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 @@ class MqttEntityDeviceInfo(Entity): 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 @@ class MqttEntity( 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 @@ class MqttEntity( 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 @@ class MqttEntity( 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 0020a020411..511b6e470ac 100644 --- a/homeassistant/components/mqtt/number.py +++ b/homeassistant/components/mqtt/number.py @@ -162,7 +162,7 @@ class MqttNumber(MqttEntity, NumberEntity, RestoreEntity): ).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 @@ class MqttNumber(MqttEntity, NumberEntity, RestoreEntity): # 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 @@ class MqttNumber(MqttEntity, NumberEntity, RestoreEntity): }, ) + 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 810c6126d52..24bed158eda 100644 --- a/homeassistant/components/mqtt/select.py +++ b/homeassistant/components/mqtt/select.py @@ -128,7 +128,7 @@ class MqttSelect(MqttEntity, SelectEntity, RestoreEntity): ).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 @@ class MqttSelect(MqttEntity, SelectEntity, RestoreEntity): # 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 @@ class MqttSelect(MqttEntity, SelectEntity, RestoreEntity): }, ) + 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 59f124155d3..6cf72279546 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -213,7 +213,7 @@ class MqttSensor(MqttEntity, SensorEntity, RestoreEntity): 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 @@ class MqttSensor(MqttEntity, SensorEntity, RestoreEntity): "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 6a268881593..e33a13545b3 100644 --- a/homeassistant/components/mqtt/siren.py +++ b/homeassistant/components/mqtt/siren.py @@ -214,7 +214,7 @@ class MqttSiren(MqttEntity, SirenEntity): 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 @@ class MqttSiren(MqttEntity, SirenEntity): # 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 @@ class MqttSiren(MqttEntity, SirenEntity): }, ) + 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 6d132b28a98..d0af533f294 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 @@ class EntitySubscription: # 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 9feba2c1d25..6576072e407 100644 --- a/homeassistant/components/mqtt/switch.py +++ b/homeassistant/components/mqtt/switch.py @@ -132,7 +132,7 @@ class MqttSwitch(MqttEntity, SwitchEntity, RestoreEntity): 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 @@ class MqttSwitch(MqttEntity, SwitchEntity, RestoreEntity): # 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 @@ class MqttSwitch(MqttEntity, SwitchEntity, RestoreEntity): }, ) + 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 b2638f8ac4b..e4152250802 100644 --- a/homeassistant/components/mqtt/tag.py +++ b/homeassistant/components/mqtt/tag.py @@ -175,7 +175,7 @@ class MQTTTagScanner: 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 @@ class MQTTTagScanner: } }, ) + 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 @@ class MQTTTagScanner: 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 5f85acc75ca..3a764ca9e45 100644 --- a/homeassistant/components/mqtt/vacuum/schema_legacy.py +++ b/homeassistant/components/mqtt/vacuum/schema_legacy.py @@ -240,7 +240,7 @@ class MqttVacuum(MqttEntity, VacuumEntity): ) } - 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 @@ class MqttVacuum(MqttEntity, VacuumEntity): 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 @@ class MqttVacuum(MqttEntity, VacuumEntity): }, ) + 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 872f4d62765..1eb763c76cd 100644 --- a/homeassistant/components/mqtt/vacuum/schema_state.py +++ b/homeassistant/components/mqtt/vacuum/schema_state.py @@ -197,7 +197,7 @@ class MqttStateVacuum(MqttEntity, StateVacuumEntity): ) } - async def _subscribe_topics(self): + def _prepare_subscribe_topics(self): """(Re)Subscribe to topics.""" topics = {} @@ -219,10 +219,14 @@ class MqttStateVacuum(MqttEntity, StateVacuumEntity): "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 f8dcd4035df..2d664bb46ee 100644 --- a/homeassistant/components/tasmota/__init__.py +++ b/homeassistant/components/tasmota/__init__.py @@ -19,6 +19,7 @@ import voluptuous as vol 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 async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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 36d8946be0b..e2ffc602ddd 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 @@ async def test_subscribe_topics(hass, mqtt_mock, caplog): 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 @@ async def test_subscribe_topics(hass, mqtt_mock, caplog): "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 @@ async def test_subscribe_topics(hass, mqtt_mock, caplog): 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 @@ async def test_modify_topics(hass, mqtt_mock, caplog): 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 @@ async def test_modify_topics(hass, mqtt_mock, caplog): "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 @@ async def test_modify_topics(hass, mqtt_mock, caplog): 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 @@ async def test_modify_topics(hass, mqtt_mock, caplog): 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 @@ async def test_qos_encoding_default(hass, mqtt_mock, caplog): 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 @@ async def test_qos_encoding_custom(hass, mqtt_mock, caplog): pass sub_state = None - sub_state = await async_subscribe_topics( + sub_state = async_prepare_subscribe_topics( hass, sub_state, { @@ -155,6 +160,7 @@ async def test_qos_encoding_custom(hass, mqtt_mock, caplog): } }, ) + 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 @@ async def test_no_change(hass, mqtt_mock, caplog): 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 ce1fdd0e7ac..e0202c63567 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 79e1bf65c86..8b17e7d020f 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 0b6b7a649a6..3bcf1cdee2c 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 3e0eb5ac16b..97ed1ee00b9 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 6c7f1043093..a091469cd81 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 c5229177b60..bb09a40b15e 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 7e35a4929f8..0181d769c2d 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 bb1cc2a6679..00c22abae4a 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -51,7 +51,6 @@ IGNORED_MODULES: Final[list[str]] = [ "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 38c9e6812b7..00ce5de2b3e 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 0181d769c2d..cd03f8dc8b5 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 6f5396f8702..c1a98c07108 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 951be9b26bb..46582334e2d 100644 --- a/homeassistant/components/broadlink/device.py +++ b/homeassistant/components/broadlink/device.py @@ -12,8 +12,9 @@ from broadlink.exceptions import ( 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 @@ class BroadlinkDevice: 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 d6bdd6675f0..e03a2d2a118 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 00000000000..887f50fb628 --- /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 fe60ed022f4..81fdd2fa916 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 @@ _TYPE_HINT_MATCHERS: dict[str, re.Pattern] = hass_enforce_type_hints._TYPE_HINT_ ("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 ee8c2f30975..b5deb8517bb 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 @@ CONFIG_SCHEMA = vol.Schema( 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_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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 d57af2d53c2..0fe04fe7eb9 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 @@ async def async_setup_entry( @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 @@ class FreeboxDevice(ScannerEntity): 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 e352146915e..0d20545fdcd 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 @@ from .const import ( 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 @@ class FreeboxRouter: 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 @@ class FreeboxRouter: 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 @@ class FreeboxRouter: 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 @@ class FreeboxRouter: 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 c5852aa22aa..46aa9ee8aa0 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 @@ class FreeboxCallSensor(FreeboxSensor): ) -> 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 76b89a17414..ee59c097f93 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 @@ class FreeboxWifiSwitch(SwitchEntity): 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 cd03f8dc8b5..e947b661618 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 00c22abae4a..2ab8739de89 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -26,7 +26,6 @@ IGNORED_MODULES: Final[list[str]] = [ "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 78ec56799a5..96094e6f75e 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 f8d4c19220b..9bb37f7a48a 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 1b2be961321..14be2d1a796 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 26c7f4f1a1a..919cf25ae82 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.const import CONF_HOST, CONF_PORT, CONF_SSL 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 5f8944f96d6..b1d5dd22942 100644 --- a/homeassistant/components/netgear/const.py +++ b/homeassistant/components/netgear/const.py @@ -5,10 +5,13 @@ from homeassistant.const import Platform 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 e3beb005845..72699768f84 100644 --- a/homeassistant/components/netgear/device_tracker.py +++ b/homeassistant/components/netgear/device_tracker.py @@ -8,9 +8,10 @@ from homeassistant.components.device_tracker.config_entry import ScannerEntity 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 - async_setup_netgear_entry(hass, entry, async_add_entities, generate_classes) + new_entities = [] + + for mac, device in router.devices.items(): + if mac in tracked: + continue + + new_entities.append(NetgearScannerEntity(coordinator, router, device)) + 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 NetgearScannerEntity(NetgearDeviceEntity, ScannerEntity): +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 @@ class NetgearScannerEntity(NetgearDeviceEntity, ScannerEntity): 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 5226218a623..d275aaf7fb2 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.const import ( 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 .const import ( ) 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 @@ class NetgearRouter: ) 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 @@ class NetgearRouter: ) 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 @@ class NetgearRouter: "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 @@ class NetgearRouter: 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 @@ class NetgearRouter: 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 @@ class NetgearDeviceEntity(Entity): 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 @@ class NetgearDeviceEntity(Entity): 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 227e6e28b09..0db0e4f19f4 100644 --- a/homeassistant/components/netgear/sensor.py +++ b/homeassistant/components/netgear/sensor.py @@ -9,8 +9,10 @@ from homeassistant.const import 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 .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 - async_setup_netgear_entry(hass, entry, async_add_entities, generate_sensor_classes) + 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) + + 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 @@ class NetgearSensorEntity(NetgearDeviceEntity, SensorEntity): 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 7777265a124..b5ae20e1123 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -701,7 +701,7 @@ class SonosSpeaker: """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 cbb778e34b1..a97a27a7453 100644 --- a/homeassistant/components/tuya/climate.py +++ b/homeassistant/components/tuya/climate.py @@ -223,7 +223,9 @@ class TuyaClimateEntity(TuyaEntity, ClimateEntity): # 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 79b01140875..7765cd27322 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 8f9526b6800..b898efe49fe 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 f02d3c03b4b..9bae6f5ebea 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 b711bca5ac3..16d7f8601c9 100644 --- a/homeassistant/components/sensibo/climate.py +++ b/homeassistant/components/sensibo/climate.py @@ -26,7 +26,6 @@ from homeassistant.components.climate.const import ( ) 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 @@ from homeassistant.const import ( 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 @@ PLATFORM_SCHEMA = PARENT_PLATFORM_SCHEMA.extend( } ) -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 @@ class SensiboClimate(CoordinatorEntity, ClimateEntity): 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 6438ea37e18..bbbdb8611e8 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 ba6bc892e40..8fb7730a4e4 100644 --- a/tests/components/atag/test_climate.py +++ b/tests/components/atag/test_climate.py @@ -6,6 +6,7 @@ from homeassistant.components.climate import ( 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 df83fa6d40b..3372e8c69fa 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 3c97f8ea47a..a3b291efd00 100644 --- a/tests/components/broadlink/test_remote.py +++ b/tests/components/broadlink/test_remote.py @@ -4,6 +4,7 @@ from unittest.mock import call 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 336dafb15a1..32212f84b34 100644 --- a/tests/components/onewire/test_switch.py +++ b/tests/components/onewire/test_switch.py @@ -4,6 +4,7 @@ from unittest.mock import MagicMock, patch 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 adfdff2d1dc..f73fdea2806 100644 --- a/tests/components/plex/test_media_search.py +++ b/tests/components/plex/test_media_search.py @@ -7,6 +7,7 @@ import pytest 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 @@ from homeassistant.components.media_player.const import ( 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 cf6fc1902e9..6ed50a833f1 100644 --- a/tests/components/renault/test_button.py +++ b/tests/components/renault/test_button.py @@ -4,7 +4,7 @@ from unittest.mock import patch 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 e0cb4413a7e..18c2eed8a7c 100644 --- a/tests/components/renault/test_select.py +++ b/tests/components/renault/test_select.py @@ -4,7 +4,11 @@ from unittest.mock import patch 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 80bf14943a9..e88df08a80c 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 84e66f0833a..e3742a3132f 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.profiles.zha as zha 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 4dc72b092e4..8ff787af7bd 100644 --- a/tests/components/zha/test_climate.py +++ b/tests/components/zha/test_climate.py @@ -25,6 +25,7 @@ from homeassistant.components.climate.const import ( 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 45c5928797d..73ab38c27ac 100644 --- a/tests/components/zha/test_cover.py +++ b/tests/components/zha/test_cover.py @@ -11,6 +11,7 @@ import zigpy.zcl.foundation as zcl_f 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 e94c028acd8..a8f5f645059 100644 --- a/tests/components/zha/test_fan.py +++ b/tests/components/zha/test_fan.py @@ -14,6 +14,7 @@ from homeassistant.components.fan import ( 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 ee437fc63c9..9c35215c889 100644 --- a/tests/components/zha/test_light.py +++ b/tests/components/zha/test_light.py @@ -9,7 +9,11 @@ import zigpy.zcl.clusters.general as general 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 c8685996c25..0669cebf128 100644 --- a/tests/components/zha/test_lock.py +++ b/tests/components/zha/test_lock.py @@ -7,6 +7,7 @@ import zigpy.zcl.clusters.closures as closures 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 ac72a00d802..336800f9ccb 100644 --- a/tests/components/zha/test_number.py +++ b/tests/components/zha/test_number.py @@ -7,6 +7,7 @@ import zigpy.types 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 17e12491f84..285bc1cd585 100644 --- a/tests/components/zha/test_siren.py +++ b/tests/components/zha/test_siren.py @@ -13,6 +13,7 @@ from homeassistant.components.siren.const import ( 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 879bc26db9f..c5cdf1a96f1 100644 --- a/tests/components/zha/test_switch.py +++ b/tests/components/zha/test_switch.py @@ -6,6 +6,7 @@ import zigpy.profiles.zha as zha 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 e947b661618..c48d0221033 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 2ab8739de89..6697adfa1d1 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -16,66 +16,289 @@ from .model import Config, Integration # 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 @@ STRICT_SETTINGS_CORE: Final[list[str]] = [ ] +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 4ef1b579e17..dd0e78007f6 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 9bb37f7a48a..602eeae6b2c 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 74c0be71d69..7e4614d127b 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 82ef6ad9429..537c106fefa 100644 --- a/homeassistant/components/sia/const.py +++ b/homeassistant/components/sia/const.py @@ -38,3 +38,4 @@ KEY_MOISTURE: Final = "moisture" 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 fee3c0b2262..311728ad578 100644 --- a/homeassistant/components/sia/sia_entity_base.py +++ b/homeassistant/components/sia/sia_entity_base.py @@ -14,7 +14,7 @@ from homeassistant.helpers.event import async_call_later 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 @@ class SIABaseEntity(RestoreEntity): 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 bb07e9ccc73..9a2ef2d4465 100644 --- a/homeassistant/components/flunearyou/__init__.py +++ b/homeassistant/components/flunearyou/__init__.py @@ -59,7 +59,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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 b592f1c416d..03e99959fd4 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 00000000000..d3e2382c7a8 --- /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 6cf72279546..a8cad4b09f8 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -236,7 +236,7 @@ class MqttSensor(MqttEntity, SensorEntity, RestoreEntity): 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 ad43a7b2353..c758b670b3d 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 6576072e407..cf5422a93eb 100644 --- a/homeassistant/components/mqtt/switch.py +++ b/homeassistant/components/mqtt/switch.py @@ -51,6 +51,8 @@ DEFAULT_OPTIMISTIC = False 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 @@ class MqttSwitch(MqttEntity, SwitchEntity, RestoreEntity): 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 @@ class MqttSwitch(MqttEntity, SwitchEntity, RestoreEntity): 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 @@ class MqttSwitch(MqttEntity, SwitchEntity, RestoreEntity): 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 de9150de1a2..5d94f349c58 100644 --- a/tests/components/mqtt/test_discovery.py +++ b/tests/components/mqtt/test_discovery.py @@ -14,9 +14,9 @@ from homeassistant.components.mqtt.abbreviations import ( 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 9519d7321ff..3eb998193a0 100644 --- a/tests/components/mqtt/test_switch.py +++ b/tests/components/mqtt/test_switch.py @@ -11,6 +11,7 @@ from homeassistant.const import ( 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 f6d60ae8cbe..f5b347d6b71 100644 --- a/homeassistant/components/mqtt/fan.py +++ b/homeassistant/components/mqtt/fan.py @@ -94,6 +94,8 @@ DEFAULT_SPEED_RANGE_MAX = 100 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 @@ class MqttFan(MqttEntity, FanEntity): 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 @@ class MqttFan(MqttEntity, FanEntity): 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 0a3b2499dc2..ecc1f15c204 100644 --- a/tests/components/mqtt/test_fan.py +++ b/tests/components/mqtt/test_fan.py @@ -27,6 +27,7 @@ from homeassistant.const import ( 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 b2c4ed4b916..e5f4cea6f88 100644 --- a/homeassistant/components/mqtt/humidifier.py +++ b/homeassistant/components/mqtt/humidifier.py @@ -66,6 +66,8 @@ DEFAULT_PAYLOAD_ON = "ON" 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 @@ class MqttHumidifier(MqttEntity, HumidifierEntity): 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 @@ class MqttHumidifier(MqttEntity, HumidifierEntity): 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 62d29c12ee8..48fe5b29a0c 100644 --- a/tests/components/mqtt/test_humidifier.py +++ b/tests/components/mqtt/test_humidifier.py @@ -28,6 +28,7 @@ from homeassistant.const import ( 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 f164abe5297..d917b379eab 100644 --- a/homeassistant/components/mqtt/light/schema_basic.py +++ b/homeassistant/components/mqtt/light/schema_basic.py @@ -109,6 +109,8 @@ CONF_WHITE_VALUE_STATE_TOPIC = "white_value_state_topic" 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 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): 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 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): @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 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): 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 2d9b8f6a388..32435948b1e 100644 --- a/homeassistant/components/mqtt/light/schema_json.py +++ b/homeassistant/components/mqtt/light/schema_json.py @@ -100,6 +100,8 @@ CONF_FLASH_TIME_SHORT = "flash_time_short" 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 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): 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 736ff98f321..b7f03fd0508 100644 --- a/homeassistant/components/mqtt/light/schema_template.py +++ b/homeassistant/components/mqtt/light/schema_template.py @@ -67,6 +67,8 @@ CONF_MIN_MIREDS = "min_mireds" 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 @@ class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity): 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 dcff826311b..a1f929244a0 100644 --- a/tests/components/mqtt/test_light.py +++ b/tests/components/mqtt/test_light.py @@ -177,6 +177,7 @@ from homeassistant.const import ( 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 baad644bf6d..2811e44618d 100644 --- a/tests/components/mqtt/test_light_json.py +++ b/tests/components/mqtt/test_light_json.py @@ -102,6 +102,7 @@ from homeassistant.const import ( 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 3a8ecd357c3..45977343b95 100644 --- a/tests/components/mqtt/test_light_template.py +++ b/tests/components/mqtt/test_light_template.py @@ -40,6 +40,7 @@ from homeassistant.const import ( 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 1eb763c76cd..494fc60fabd 100644 --- a/homeassistant/components/mqtt/vacuum/schema_state.py +++ b/homeassistant/components/mqtt/vacuum/schema_state.py @@ -206,8 +206,12 @@ class MqttStateVacuum(MqttEntity, StateVacuumEntity): 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 5011f279470..a1b90f52c37 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 00ce5de2b3e..cd74910415c 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 b66e6d4676e..ba152c5d840 100644 --- a/homeassistant/components/isy994/__init__.py +++ b/homeassistant/components/isy994/__init__.py @@ -16,7 +16,7 @@ from homeassistant.const import ( 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 5cf7d6b2b76..23e77ba849d 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 @@ from pyisy.constants import ( 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 @@ from homeassistant.components.binary_sensor import ( 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 @@ class ISYInsteonBinarySensorEntity(ISYBinarySensorEntity): 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 @@ class ISYInsteonBinarySensorEntity(ISYBinarySensorEntity): 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 @@ class ISYInsteonBinarySensorEntity(ISYBinarySensorEntity): 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 @@ class ISYInsteonBinarySensorEntity(ISYBinarySensorEntity): 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 @@ class ISYInsteonBinarySensorEntity(ISYBinarySensorEntity): 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 @@ class ISYInsteonBinarySensorEntity(ISYBinarySensorEntity): 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 @@ class ISYBinarySensorHeartbeat(ISYNodeEntity, BinarySensorEntity): """ 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 @@ class ISYBinarySensorHeartbeat(ISYNodeEntity, BinarySensorEntity): # 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 @@ class ISYBinarySensorHeartbeat(ISYNodeEntity, BinarySensorEntity): 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 @@ class ISYBinarySensorHeartbeat(ISYNodeEntity, BinarySensorEntity): 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 @@ class ISYBinarySensorHeartbeat(ISYNodeEntity, BinarySensorEntity): 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 98c20bb441a..00a02e5c210 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 @@ from pyisy.constants import ( 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 @@ from homeassistant.components.climate.const import ( 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 @@ class ISYThermostatEntity(ISYNodeEntity, ClimateEntity): 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 @@ class ISYThermostatEntity(ISYNodeEntity, ClimateEntity): 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 @@ class ISYThermostatEntity(ISYNodeEntity, ClimateEntity): 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 @@ class ISYThermostatEntity(ISYNodeEntity, ClimateEntity): 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 @@ class ISYThermostatEntity(ISYNodeEntity, ClimateEntity): 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 @@ class ISYThermostatEntity(ISYNodeEntity, ClimateEntity): """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 4e700df24cb..866ec800402 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 @@ from .const import ( _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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class OptionsFlowHandler(config_entries.OptionsFlow): """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 470aaa64c64..8ca1ac786f8 100644 --- a/homeassistant/components/isy994/const.py +++ b/homeassistant/components/isy994/const.py @@ -204,7 +204,7 @@ UOM_PERCENTAGE = "51" # 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 0fcfb30a775..f00128b6d15 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 @@ class ISYCoverProgramEntity(ISYProgramEntity, CoverEntity): """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 aca0dbf5d3d..e5db8de5872 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 @@ from pyisy.constants import ( 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 @@ from .const import DOMAIN 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 @@ class ISYEntity(Entity): ) @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 @@ class ISYEntity(Entity): 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 @@ class ISYEntity(Entity): basename = node.name device_info = DeviceInfo( - identifiers={}, manufacturer="Unknown", model="Unknown", name=basename, @@ -99,25 +102,30 @@ class ISYEntity(Entity): ) 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 @@ class ISYEntity(Entity): 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 @@ class ISYNodeEntity(ISYEntity): 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 @@ class ISYNodeEntity(ISYEntity): 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 @@ class ISYNodeEntity(ISYEntity): ) 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 @@ class ISYNodeEntity(ISYEntity): ) 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 @@ class ISYNodeEntity(ISYEntity): 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 @@ class ISYNodeEntity(ISYEntity): 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 28d0675a184..bf4d48ad3e8 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 @@ class ISYFanEntity(ISYNodeEntity, FanEntity): 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 @@ class ISYFanEntity(ISYNodeEntity, FanEntity): 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 @@ class ISYFanProgramEntity(ISYProgramEntity, FanEntity): @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 d1790fcc13c..6d0a1d303bb 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.fan import DOMAIN as FAN 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 @@ from .const import ( 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 2fd98b6f177..640442c3f19 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 @@ class ISYLightEntity(ISYNodeEntity, LightEntity, RestoreEntity): 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 @@ class ISYLightEntity(ISYNodeEntity, LightEntity, RestoreEntity): 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 @@ class ISYLightEntity(ISYNodeEntity, LightEntity, RestoreEntity): 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 @@ class ISYLightEntity(ISYNodeEntity, LightEntity, RestoreEntity): _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 @@ class ISYLightEntity(ISYNodeEntity, LightEntity, RestoreEntity): ): 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 e2befc57487..4de5cdaa05b 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 @@ class ISYLockProgramEntity(ISYProgramEntity, LockEntity): """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 466a7334775..d9751fd707b 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 @@ class ISYSensorEntity(ISYNodeEntity, SensorEntity): 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 @@ class ISYSensorEntity(ISYNodeEntity, SensorEntity): 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 @@ class ISYSensorEntity(ISYNodeEntity, SensorEntity): 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 @@ class ISYSensorVariableEntity(ISYEntity, SensorEntity): 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 @@ class ISYSensorVariableEntity(ISYEntity, SensorEntity): } @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 a1dff594a1f..8323394803f 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 @@ SERVICE_RUN_NETWORK_RESOURCE_SCHEMA = vol.All( @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 @@ def async_setup_services(hass: HomeAssistant): # noqa: C901 """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 @@ def async_setup_services(hass: HomeAssistant): # noqa: C901 @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 3e72dd6f0ec..a92be5d4d23 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 @@ class ISYSwitchProgramEntity(ISYProgramEntity, SwitchEntity): """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 f550b8ed07b..a8497ba0b27 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 c48d0221033..cc5d9b0e5e4 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 6697adfa1d1..6b14803b499 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -101,19 +101,6 @@ IGNORED_MODULES: Final[list[str]] = [ "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 cd74910415c..e84e01b1803 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 974cb5e1dfc..eeca98167d0 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -136,7 +136,7 @@ class HomeKitEntity(Entity): @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 e8179b0bc6b..53aee8561e4 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.characteristics import ( 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 @@ TARGET_HEATER_COOLER_STATE_HASS_TO_HOMEKIT = { 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 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): 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 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): 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 @@ class HomeKitHeaterCoolerEntity(HomeKitEntity, ClimateEntity): 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 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): 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 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): 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 @@ class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): 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 8bd5351906c..e4ad06f322a 100644 --- a/homeassistant/components/homekit_controller/connection.py +++ b/homeassistant/components/homekit_controller/connection.py @@ -13,8 +13,8 @@ from aiohomekit.exceptions import ( 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 @@ AddServiceCb = Callable[[Service], bool] 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 @@ class HKDevice: 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 @@ class HKDevice: } 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 @@ class HKDevice: 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 @@ class HKDevice: (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 @@ class HKDevice: } 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 @@ class HKDevice: 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 a69d189ebd5..aa2765d9be5 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 homeassistant.helpers.typing import ConfigType from .const import DOMAIN, KNOWN_DEVICES, TRIGGERS +if TYPE_CHECKING: + from .connection import HKDevice + TRIGGER_TYPES = { "doorbell", "button1", @@ -225,7 +228,7 @@ async def async_setup_triggers_for_entry(hass: HomeAssistant, config_entry): 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 bc922dd4ec1..fcca3e54725 100644 --- a/homeassistant/components/homekit_controller/humidifier.py +++ b/homeassistant/components/homekit_controller/humidifier.py @@ -8,6 +8,8 @@ from aiohomekit.model.services import Service, ServicesTypes 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 @@ class HomeKitHumidifier(HomeKitEntity, HumidifierEntity): @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 @@ class HomeKitDehumidifier(HomeKitEntity, HumidifierEntity): @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 fcb36d3c54a..8cc6ce2b575 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 7318343b643..6314efe9dc4 100644 --- a/homeassistant/components/homekit_controller/media_player.py +++ b/homeassistant/components/homekit_controller/media_player.py @@ -121,7 +121,7 @@ class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity): ) @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 a473e30fe6d..5fcb5027640 100644 --- a/homeassistant/components/homekit_controller/number.py +++ b/homeassistant/components/homekit_controller/number.py @@ -9,6 +9,11 @@ from __future__ import annotations 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 @@ class HomeKitNumber(CharacteristicEntity, NumberEntity): @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 @@ class HomeKitEcobeeFanModeNumber(CharacteristicEntity, NumberEntity): @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 fc6970f078a..9372764a88a 100644 --- a/homeassistant/components/homekit_controller/storage.py +++ b/homeassistant/components/homekit_controller/storage.py @@ -51,13 +51,14 @@ class EntityMapStorage: 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 cc5d9b0e5e4..32ee9352326 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 602eeae6b2c..79407a8df6d 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 7e4614d127b..d71ccbfadc5 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 a133ec6c2dc..9a0566531c3 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -150,86 +150,62 @@ SERVICES = ( 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 8e54e294f4b..5a80691fa46 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -742,7 +742,7 @@ class _ScriptRun: 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 2ee4213a688..5bb4833a796 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 55af1619da5..b971a428a76 100644 --- a/homeassistant/components/guardian/__init__.py +++ b/homeassistant/components/guardian/__init__.py @@ -3,7 +3,7 @@ from __future__ import annotations 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 @@ import voluptuous as vol 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.const import ( ) 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 @@ SERVICES = ( 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 @@ PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR, Platform.SWITCH] @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 fccd8979631..8d91b984d46 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 @@ from tesla_powerwall import ( APIError, MissingAttributeError, Powerwall, + PowerwallError, PowerwallUnreachableError, ) @@ -19,12 +21,14 @@ 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 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 cb9929a890e..9814a52d8a0 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 import config_entries, core, exceptions 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 8cc0cbc27cd..b2f0e3afe80 100644 --- a/homeassistant/components/powerwall/const.py +++ b/homeassistant/components/powerwall/const.py @@ -26,6 +26,7 @@ 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" diff --git a/homeassistant/components/powerwall/manifest.json b/homeassistant/components/powerwall/manifest.json index fd17557abe1..55c7ab41e64 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 e1b2f2dbd3b..8995a0f956e 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 3be711d94c5..04279759888 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 f05a7f73e50..701117cb562 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -218,13 +218,7 @@ DHCP = [ }, { "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 79407a8df6d..c858f443769 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 d71ccbfadc5..9f5e8578696 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 9d253c3a74b..1eac0319819 100644 --- a/tests/components/powerwall/mocks.py +++ b/tests/components/powerwall/mocks.py @@ -16,6 +16,8 @@ from tesla_powerwall import ( 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 29a04a70085..3ef9e9c0fd1 100644 --- a/tests/components/powerwall/test_config_flow.py +++ b/tests/components/powerwall/test_config_flow.py @@ -12,8 +12,13 @@ from homeassistant import config_entries 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 0890a962e5b..39e255bb715 100644 --- a/homeassistant/components/unifiprotect/config_flow.py +++ b/homeassistant/components/unifiprotect/config_flow.py @@ -24,6 +24,7 @@ from homeassistant.data_entry_flow import FlowResult 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 @@ class ProtectFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 a1609984be3..68d90ff82eb 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 f8a38971a95..b08c521537d 100644 --- a/homeassistant/components/diagnostics/__init__.py +++ b/homeassistant/components/diagnostics/__init__.py @@ -4,7 +4,7 @@ from __future__ import annotations 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 288fcc560c3..0137e26a8a2 100644 --- a/pylint/plugins/hass_enforce_type_hints.py +++ b/pylint/plugins/hass_enforce_type_hints.py @@ -10,6 +10,7 @@ from pylint.interfaces import IAstroidChecker from pylint.lint import PyLinter from homeassistant.const import Platform +from homeassistant.helpers.typing import UNDEFINED @dataclass @@ -39,9 +40,9 @@ _MODULE_FILTERS: dict[str, re.Pattern] = { 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 @@ _METHOD_MATCH: list[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 832128a8b3e..70e1691af92 100644 --- a/homeassistant/components/tuya/vacuum.py +++ b/homeassistant/components/tuya/vacuum.py @@ -37,6 +37,7 @@ TUYA_MODE_RETURN_HOME = "chargego" 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 @@ TUYA_STATUS_TO_HA = { "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 952cb96fcd8..fbb22bb7397 100644 --- a/homeassistant/components/esphome/switch.py +++ b/homeassistant/components/esphome/switch.py @@ -5,7 +5,7 @@ from typing import Any 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 @@ class EsphomeSwitch(EsphomeEntity[SwitchInfo, SwitchState], SwitchEntity): """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 4f1e6eff032..8a9b13648e9 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 438fa0eba8d..3f0b9516ead 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 c858f443769..3f978e595d7 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 9f5e8578696..734a452a854 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 dede4b82c02..fa4096cb4ff 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -220,8 +220,8 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator): """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 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator): 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 ac9a9c83be2..15e57f223e9 100644 --- a/homeassistant/components/tuya/base.py +++ b/homeassistant/components/tuya/base.py @@ -72,9 +72,11 @@ class IntegerTypeData: 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 @@ class TuyaEntity(Entity): 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 fa4096cb4ff..2cd6616f134 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -574,6 +574,13 @@ class AvmWrapper(FritzBoxTools): 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 @@ class AvmWrapper(FritzBoxTools): 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 4305ee4d7cb..f35eca6b914 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 378ad399ffe..042c6b9aa99 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 3f978e595d7..0957c0bf3b0 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 734a452a854..f25e984e287 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 0f312d6a37a..2f9583f329c 100644 --- a/homeassistant/components/cast/home_assistant_cast.py +++ b/homeassistant/components/cast/home_assistant_cast.py @@ -6,14 +6,16 @@ import voluptuous as vol 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 00000000000..02280ebd182 --- /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 67b5454b6e1..a799a6d1d36 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 00000000000..d5b8e43d2bb --- /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 16d7f8601c9..b0cee4fbac0 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 @@ SENSIBO_TO_HA = { 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 @@ class SensiboClimate(CoordinatorEntity, ClimateEntity): 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 @@ class SensiboClimate(CoordinatorEntity, ClimateEntity): ) 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 b5ae20e1123..e40fe901b09 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -400,7 +400,12 @@ class SonosSpeaker: ) 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 bedcc5f821c..2db0034b697 100644 --- a/homeassistant/components/dnsip/config_flow.py +++ b/homeassistant/components/dnsip/config_flow.py @@ -33,6 +33,13 @@ DATA_SCHEMA = vol.Schema( 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 @@ class DnsIPConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class DnsIPConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): }, ) + 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 06672e6fb68..cd95c9db27f 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 7b2e2f9e6c7..2c773375860 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 59dcb81aa94..f4684eb1cc4 100644 --- a/tests/components/dnsip/test_config_flow.py +++ b/tests/components/dnsip/test_config_flow.py @@ -7,6 +7,7 @@ from aiodns.error import DNSError 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 26cce2c736d..e941a2ffafa 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 d73e03b44d4..546bb055ca8 100644 --- a/homeassistant/components/lutron_caseta/__init__.py +++ b/homeassistant/components/lutron_caseta/__init__.py @@ -227,7 +227,7 @@ def _async_subscribe_pico_remote_events( 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_subscribe_pico_remote_events( 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 29133caadb8..79013ab88a4 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 00000000000..1339995b647 --- /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 00000000000..0b5511ab845 --- /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 00000000000..847c45368fd --- /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 fb65cbae3f8..3eda86b60cb 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 4ad186fd8b4..c113c987cea 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 e430a82e67e..14bbe0e5dd8 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 00000000000..847c45368fd --- /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 02c3aa97f46..3c28dd6ca6a 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 327a580fc13..41130cbdd39 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 00000000000..606c529facc --- /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 b648ed0573a..12f69b931c8 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 97a0f70ec26..b7fc1d36b52 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 00000000000..6baa0721470 --- /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 00000000000..d5b8a58a422 --- /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 00000000000..5ef5b36c80d --- /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 00000000000..2c314f33c2d --- /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 ffc9c5ba4ec..4178ce86cb6 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 00000000000..f126937f2fe --- /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 00000000000..8bd1aa046be --- /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 00000000000..dfbcfc4711f --- /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 00000000000..847c45368fd --- /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 00000000000..533218a036a --- /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 87e64d0db17..89246ded4a1 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 00000000000..136c083b7fb --- /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 43b3557af3a..569372f2579 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 00000000000..5e712fccbd9 --- /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 00000000000..89900954d12 --- /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 5252249c79b..1fc438ae03f 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 00000000000..d5b8a58a422 --- /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 00000000000..74f09833a81 --- /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 00000000000..d5b8a58a422 --- /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 00000000000..847c45368fd --- /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 00000000000..a9f534742c5 --- /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 add7d39e39a..a412a0914c6 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 00000000000..0015010c63c --- /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 5c687ceb5b6..0c40b77035d 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 00000000000..f7927ce0f96 --- /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 00000000000..847c45368fd --- /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 56cc2ce2752..6084975c60e 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 aecb2ee553f..77ccacd89d6 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 00000000000..d5b8a58a422 --- /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 00000000000..b97053efa85 --- /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 00000000000..686be80ba76 --- /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 00000000000..47c25d7ddff --- /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 40d3a848254..d7732fb29f9 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 00000000000..847c45368fd --- /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 00000000000..32140df6fa7 --- /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 36a89b17449..251383c2115 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 00000000000..847c45368fd --- /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 00000000000..847c45368fd --- /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 5ac91833471..13afb78596f 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 e7716b1beba..b4a731fc1f6 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 00000000000..f8853cd2d47 --- /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 00000000000..847c45368fd --- /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 00000000000..d5b8a58a422 --- /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 aecb2ee553f..19873f86a31 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 00000000000..d5b8a58a422 --- /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 d533b6e4471..f0621dde7be 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 835523c59d7..15d48e9c63c 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 6cd33d9fbb1..2982f4997fe 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 00000000000..61ccae30de9 --- /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 00000000000..cb56d003e52 --- /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 00000000000..7fc549d5a9a --- /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 668a292e39b..68e185b7339 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 00000000000..5d04c17f932 --- /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 0cde52b9297..d7b4acd20c1 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 16e7f3b5d17..c71df1f9b4e 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 00000000000..371cb62d9b4 --- /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 00000000000..847c45368fd --- /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 00000000000..ef1398553b5 --- /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 00000000000..ae7c5ea3f30 --- /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 00000000000..309e8428413 --- /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 00000000000..847c45368fd --- /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 aecb2ee553f..bf45bc1b864 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 00000000000..d5b8a58a422 --- /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 f30b504897d..76192bb3427 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 00000000000..847c45368fd --- /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 00000000000..d5b8a58a422 --- /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 033f78316a9..debd53f9f8a 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 c8020069676..bbba4d0bf5e 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 98eb25ca17a..511bc7825d8 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 8975694ca95..6f53b1ef575 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 1da49f708d9..c3eea93473a 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 00000000000..c106bc179b3 --- /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 6d682ff545b..e02eefac9db 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 00000000000..847c45368fd --- /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 00000000000..ebf05b607dd --- /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 40037f5e3eb..74db188f67a 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 f64e478a485..d1473074cca 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 26695d002b3..14c3fe70ac4 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 00000000000..143386f4703 --- /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 4541308b526..5900d42cba9 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 d5323c2c074..65fe6161ed0 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 00000000000..66b7784c3fc --- /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 59827459f94..7f36d019843 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 00000000000..847c45368fd --- /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 00000000000..847c45368fd --- /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 00000000000..d22c90b4e10 --- /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 00000000000..3a397e1f7d3 --- /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 00000000000..7fa228d894a --- /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 00000000000..a7911f7f907 --- /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 bf736e09664..402d66a0d55 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 00000000000..d67f873296b --- /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 98220340205..dc450b8f5ae 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 00000000000..d5b8a58a422 --- /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 00000000000..19fbf894f8d --- /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 fdb3d8a981d..9879388d0d8 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 bfb955dca5f..f8d2ba4acb0 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 00000000000..f7653b352e4 --- /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 00000000000..d5b8a58a422 --- /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 4c3917b4cae..13413fd102c 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 2419418479b..0e5ba9af217 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 00000000000..f605133f204 --- /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 abc844c32d9..4163b736ac8 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 00000000000..847c45368fd --- /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 00000000000..847c45368fd --- /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 00000000000..847c45368fd --- /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 00000000000..847c45368fd --- /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 00000000000..821295d2c82 --- /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 d35fc5a2b08..3d7f30e39df 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 00000000000..c106bc179b3 --- /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 f4104fe78a5..19e12f287ff 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 c71e66f6434..1d8e5a1c163 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 1663d4975a9..028a6302d0b 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 a01b5de133a..7a41c914e8a 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 9840d89dc9d..fcd769dc8a4 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 481fc429cb0..888efbf2bfd 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 3f57f4bbe6f..de7ed5da502 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 3919edbd340..71a6865287c 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 19c86af22ed..bf3738a7fb3 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 2cd6616f134..667d94695d3 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -18,6 +18,7 @@ from fritzconnection.core.exceptions import ( ) 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 @@ from .const import ( SERVICE_CLEANUP, SERVICE_REBOOT, SERVICE_RECONNECT, + SERVICE_SET_GUEST_WIFI_PW, MeshRoles, ) @@ -150,6 +152,7 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator): 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 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator): ) 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 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator): """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 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator): 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 59200e07c78..0a4e9fd6cd8 100644 --- a/homeassistant/components/fritz/const.py +++ b/homeassistant/components/fritz/const.py @@ -49,6 +49,7 @@ FRITZ_SERVICES = "fritz_services" 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 e32f4c7ffd7..c4e7de8df5e 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 @@ from .const import ( 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_setup_services(hass: HomeAssistant) -> 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 2375aa71f57..3c7ed643841 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 7eb75f54a55..583fd0a70c6 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 0957c0bf3b0..7721aa70812 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 f25e984e287..fdb18ea080d 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 f5fc8bc61cc..b13c8ca19b2 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 7721aa70812..71b83158f23 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 fdb18ea080d..c2522431b98 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 110753ac15f..b73e5444b22 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 317babfe74c..661e218a1ad 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 2edd750a6e0..423bb1b55d7 100644 --- a/tests/components/google_assistant/__init__.py +++ b/tests/components/google_assistant/__init__.py @@ -383,8 +383,8 @@ DEMO_DEVICES = [ "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 ecc4e13e54e..f4fd65748f1 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 71b83158f23..a625d305289 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 c2522431b98..168e9d10d51 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 a029fa35a65..ea2b34a874f 100644 --- a/homeassistant/components/zwave/light.py +++ b/homeassistant/components/zwave/light.py @@ -123,7 +123,7 @@ class ZwaveDimmer(ZWaveDeviceEntity, LightEntity): 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 35128ccc69a..74c541f4d5a 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 acc4577fa8a..c3c173f778e 100644 --- a/homeassistant/components/iotawatt/sensor.py +++ b/homeassistant/components/iotawatt/sensor.py @@ -8,7 +8,6 @@ import logging from iotawattpy.sensor import Sensor from homeassistant.components.sensor import ( - ATTR_LAST_RESET, SensorDeviceClass, SensorEntity, SensorEntityDescription, @@ -211,6 +210,12 @@ class IotaWattSensor(update_coordinator.CoordinatorEntity, SensorEntity): 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 @@ class IotaWattSensor(update_coordinator.CoordinatorEntity, SensorEntity): 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 7142949f0c6..56bc59e546f 100644 --- a/homeassistant/components/tuya/binary_sensor.py +++ b/homeassistant/components/tuya/binary_sensor.py @@ -194,6 +194,16 @@ BINARY_SENSORS: dict[str, tuple[TuyaBinarySensorEntityDescription, ...]] = { ), 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 a97a27a7453..17e6e967a34 100644 --- a/homeassistant/components/tuya/climate.py +++ b/homeassistant/components/tuya/climate.py @@ -79,6 +79,12 @@ CLIMATE_DESCRIPTIONS: dict[str, 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 7765cd27322..e38671ae912 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 95ed463ee81..06c4a783066 100644 --- a/homeassistant/components/tuya/sensor.py +++ b/homeassistant/components/tuya/sensor.py @@ -447,6 +447,9 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = { ), *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 619e85cbdd4..4559bdf3364 100644 --- a/homeassistant/components/tuya/switch.py +++ b/homeassistant/components/tuya/switch.py @@ -443,6 +443,22 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { 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 | 681 +++++++++++++----- 2 files changed, 623 insertions(+), 300 deletions(-) diff --git a/homeassistant/components/deconz/binary_sensor.py b/homeassistant/components/deconz/binary_sensor.py index cda743f0893..fd674dc1cba 100644 --- a/homeassistant/components/deconz/binary_sensor.py +++ b/homeassistant/components/deconz/binary_sensor.py @@ -7,7 +7,6 @@ from dataclasses import dataclass from pydeconz.sensor import ( Alarm, CarbonMonoxide, - DeconzBinarySensor as PydeconzBinarySensor, DeconzSensor as PydeconzSensor, Fire, GenericFlag, @@ -34,21 +33,21 @@ from .const import ATTR_DARK, ATTR_ON 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 @@ BINARY_SENSOR_DESCRIPTIONS = [ ), 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 @@ async def async_setup_entry( | 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 @@ class DeconzBinarySensor(DeconzDevice, BinarySensorEntity): 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 11f9483e277..0bd308caeed 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 @@ from homeassistant.components.deconz.const import ( 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_DATA = [ + ( # Alarm binary 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": "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", + }, + "websocket_event": {"alarm": True}, + "next_state": STATE_ON, + }, + ), + ( # Carbon monoxide binary sensor + { + "config": { + "battery": 100, + "on": True, + "pending": [], + "reachable": True, + }, + "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, + }, + ), +] + + +@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) + + 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 len(hass.states.async_all()) == expected["entity_count"] + + # Verify state data + + 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 + + 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", + "e": "changed", + "r": "sensors", + "id": "1", + "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"] + + # 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 + + +async def test_not_allow_clip_sensor(hass, aioclient_mock): + """Test that CLIP sensors are not allowed.""" 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", - }, - "2": { - "name": "Temperature sensor", - "type": "ZHATemperature", - "state": {"temperature": False}, - "config": {}, - "uniqueid": "00:00:00:00:00:00:00:01-00", - }, - "3": { "name": "CLIP presence sensor", "type": "CLIPPresence", "state": {"presence": False}, "config": {}, "uniqueid": "00:00:00:00:00:00:00:02-00", }, - "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", - }, } } + with patch.dict(DECONZ_WEB_REQUEST, data): - config_entry = await setup_deconz_integration(hass, aioclient_mock) + await setup_deconz_integration( + hass, aioclient_mock, options={CONF_ALLOW_CLIP_SENSOR: False} + ) - 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 - - 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 - - await hass.config_entries.async_unload(config_entry.entry_id) - - assert hass.states.get("binary_sensor.presence_sensor").state == STATE_UNAVAILABLE - - 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_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 = er.async_get(hass) - - 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 - ) - - event_changed_sensor = { - "t": "event", - "e": "changed", - "r": "sensors", - "id": "1", - "state": {"tampered": True}, - } - await mock_deconz_websocket(data=event_changed_sensor) - await hass.async_block_till_done() - - assert hass.states.get("binary_sensor.presence_sensor_tampered").state == STATE_ON - - await hass.config_entries.async_unload(config_entry.entry_id) - - assert ( - hass.states.get("binary_sensor.presence_sensor_tampered").state - == STATE_UNAVAILABLE - ) - - 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.""" - 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", - }, - } - } - 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 - ) - - 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 e935cf004be..71a5b89d534 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 a625d305289..4f0e477938b 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 168e9d10d51..be7d3602893 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 347722be0a5..0bf10ca71c6 100644 --- a/homeassistant/components/recorder/statistics.py +++ b/homeassistant/components/recorder/statistics.py @@ -119,8 +119,6 @@ QUERY_STATISTIC_META_ID = [ 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 296409d984f..25590c712d9 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 33b719f88c9..131e02ceba0 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 4f0e477938b..1da5ead8fe0 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 be7d3602893..9a296d256ce 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 9ff389325ce..964f19e1d7c 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 eaea908e358..5d7c67d621b 100644 --- a/homeassistant/components/mqtt/alarm_control_panel.py +++ b/homeassistant/components/mqtt/alarm_control_panel.py @@ -328,8 +328,7 @@ class MqttAlarm(MqttEntity, alarm.AlarmControlPanelEntity): """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 993251606ed..c4b4b19b120 100644 --- a/homeassistant/components/mqtt/button.py +++ b/homeassistant/components/mqtt/button.py @@ -112,8 +112,7 @@ class MqttButton(MqttEntity, ButtonEntity): 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 02d4f267fe8..91d7cdd772d 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -708,8 +708,7 @@ class MqttClimate(MqttEntity, ClimateEntity): 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 dfb48fb89e2..4dfa9e20798 100644 --- a/homeassistant/components/mqtt/cover.py +++ b/homeassistant/components/mqtt/cover.py @@ -534,8 +534,7 @@ class MqttCover(MqttEntity, CoverEntity): 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 @@ class MqttCover(MqttEntity, CoverEntity): 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 @@ class MqttCover(MqttEntity, CoverEntity): 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 @@ class MqttCover(MqttEntity, CoverEntity): "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 @@ class MqttCover(MqttEntity, CoverEntity): 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 @@ class MqttCover(MqttEntity, CoverEntity): } 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 @@ class MqttCover(MqttEntity, CoverEntity): } 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 3e32d301b70..e1001ab7b04 100644 --- a/homeassistant/components/mqtt/debug_info.py +++ b/homeassistant/components/mqtt/debug_info.py @@ -3,13 +3,18 @@ from __future__ import annotations 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 log_messages( 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 f5b347d6b71..6fe36cd5fcd 100644 --- a/homeassistant/components/mqtt/fan.py +++ b/homeassistant/components/mqtt/fan.py @@ -544,8 +544,7 @@ class MqttFan(MqttEntity, FanEntity): 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 @@ class MqttFan(MqttEntity, FanEntity): 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 @@ class MqttFan(MqttEntity, FanEntity): 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 @@ class MqttFan(MqttEntity, FanEntity): 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 @@ class MqttFan(MqttEntity, FanEntity): 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 e5f4cea6f88..c9a5341a43f 100644 --- a/homeassistant/components/mqtt/humidifier.py +++ b/homeassistant/components/mqtt/humidifier.py @@ -419,8 +419,7 @@ class MqttHumidifier(MqttEntity, HumidifierEntity): 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 @@ class MqttHumidifier(MqttEntity, HumidifierEntity): 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 @@ class MqttHumidifier(MqttEntity, HumidifierEntity): 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 @@ class MqttHumidifier(MqttEntity, HumidifierEntity): 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 d917b379eab..1ff08a49ab1 100644 --- a/homeassistant/components/mqtt/light/schema_basic.py +++ b/homeassistant/components/mqtt/light/schema_basic.py @@ -833,8 +833,7 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): 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 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): 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 32435948b1e..52b840fcfaf 100644 --- a/homeassistant/components/mqtt/light/schema_json.py +++ b/homeassistant/components/mqtt/light/schema_json.py @@ -638,8 +638,7 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): 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 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): 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 b7f03fd0508..5700a8ab868 100644 --- a/homeassistant/components/mqtt/light/schema_template.py +++ b/homeassistant/components/mqtt/light/schema_template.py @@ -392,8 +392,7 @@ class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity): 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 @@ class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity): 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 89917f4cc5c..788c6be1fef 100644 --- a/homeassistant/components/mqtt/lock.py +++ b/homeassistant/components/mqtt/lock.py @@ -179,8 +179,7 @@ class MqttLock(MqttEntity, LockEntity): 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 @@ class MqttLock(MqttEntity, LockEntity): 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 @@ class MqttLock(MqttEntity, LockEntity): 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 1f4bbe9d949..722bfd51c9f 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -42,7 +42,7 @@ from homeassistant.helpers.entity import ( ) 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 @@ from .const import ( 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 @@ from .discovery import ( 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 @@ class MqttDiscoveryUpdate(Entity): # 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 @@ class MqttEntity( 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 511b6e470ac..6f9c4c38ead 100644 --- a/homeassistant/components/mqtt/number.py +++ b/homeassistant/components/mqtt/number.py @@ -257,8 +257,7 @@ class MqttNumber(MqttEntity, NumberEntity, RestoreEntity): 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 24bed158eda..58e6e7e4d64 100644 --- a/homeassistant/components/mqtt/select.py +++ b/homeassistant/components/mqtt/select.py @@ -183,8 +183,7 @@ class MqttSelect(MqttEntity, SelectEntity, RestoreEntity): 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 e33a13545b3..b3c5df157c9 100644 --- a/homeassistant/components/mqtt/siren.py +++ b/homeassistant/components/mqtt/siren.py @@ -328,8 +328,7 @@ class MqttSiren(MqttEntity, SirenEntity): 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 cf5422a93eb..906901b6080 100644 --- a/homeassistant/components/mqtt/switch.py +++ b/homeassistant/components/mqtt/switch.py @@ -197,8 +197,7 @@ class MqttSwitch(MqttEntity, SwitchEntity, RestoreEntity): 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 @@ class MqttSwitch(MqttEntity, SwitchEntity, RestoreEntity): 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 3a764ca9e45..087de1086b5 100644 --- a/homeassistant/components/mqtt/vacuum/schema_legacy.py +++ b/homeassistant/components/mqtt/vacuum/schema_legacy.py @@ -388,8 +388,7 @@ class MqttVacuum(MqttEntity, VacuumEntity): 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 @@ class MqttVacuum(MqttEntity, VacuumEntity): 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 @@ class MqttVacuum(MqttEntity, VacuumEntity): 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 @@ class MqttVacuum(MqttEntity, VacuumEntity): 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 @@ class MqttVacuum(MqttEntity, VacuumEntity): 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 @@ class MqttVacuum(MqttEntity, VacuumEntity): 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 @@ class MqttVacuum(MqttEntity, VacuumEntity): 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 @@ class MqttVacuum(MqttEntity, VacuumEntity): ) 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 @@ class MqttVacuum(MqttEntity, VacuumEntity): 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 494fc60fabd..e5c138c96ff 100644 --- a/homeassistant/components/mqtt/vacuum/schema_state.py +++ b/homeassistant/components/mqtt/vacuum/schema_state.py @@ -260,8 +260,7 @@ class MqttStateVacuum(MqttEntity, StateVacuumEntity): """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 @@ class MqttStateVacuum(MqttEntity, StateVacuumEntity): """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 @@ class MqttStateVacuum(MqttEntity, StateVacuumEntity): """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 @@ class MqttStateVacuum(MqttEntity, StateVacuumEntity): 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 @@ class MqttStateVacuum(MqttEntity, StateVacuumEntity): """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 @@ class MqttStateVacuum(MqttEntity, StateVacuumEntity): """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 @@ class MqttStateVacuum(MqttEntity, StateVacuumEntity): """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 @@ class MqttStateVacuum(MqttEntity, StateVacuumEntity): 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 16e46faaef8..091048513c7 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 39685db2afc..aa726f4fff2 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 a533e0f0ec7..e4997085ce2 100644 --- a/tests/components/mqtt/test_button.py +++ b/tests/components/mqtt/test_button.py @@ -18,6 +18,7 @@ from .test_common import ( 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 95e8c467a52..936e4ef4664 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 3b2da69f94b..16c765dc51a 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 78c37b1105a..758cdd801ae 100644 --- a/tests/components/mqtt/test_common.py +++ b/tests/components/mqtt/test_common.py @@ -43,6 +43,8 @@ DEFAULT_CONFIG_DEVICE_INFO_MAC = { "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 0d24f805cc1..59e03dadfe8 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 a5359563d92..972b0678ed2 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 ecc1f15c204..9ce5e54262e 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 48fe5b29a0c..f8685898ed5 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 9101b895218..3cb49598e8c 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 808212014c7..d6e1524fc40 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 a1f929244a0..0eb77990d32 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 2811e44618d..8f2dce599ac 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 45977343b95..c68ed8e7f35 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 f29222f97d5..35849c4f9fc 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 c233bf14ab5..70bc1b40e75 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 c09c0aebca8..f6f005bbfb5 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 c758b670b3d..b556bcf7537 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 e500bdb6ea7..7f174582a46 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 a1b90f52c37..7b8928ccb32 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 3eb998193a0..79ee56998e8 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 bca041c6a27..b50d87a940d 100644 --- a/homeassistant/components/homewizard/__init__.py +++ b/homeassistant/components/homewizard/__init__.py @@ -3,10 +3,11 @@ import logging 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 17f87680c62..45a912fefec 100644 --- a/homeassistant/components/homewizard/config_flow.py +++ b/homeassistant/components/homewizard/config_flow.py @@ -28,6 +28,21 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): } ) + 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 7364a0e632e..f416027da4a 100644 --- a/tests/components/homewizard/test_config_flow.py +++ b/tests/components/homewizard/test_config_flow.py @@ -12,6 +12,8 @@ from homeassistant.data_entry_flow import RESULT_TYPE_ABORT, RESULT_TYPE_CREATE_ 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 f7aa4de7ade..87a02a446e9 100644 --- a/tests/components/homewizard/test_init.py +++ b/tests/components/homewizard/test_init.py @@ -4,9 +4,11 @@ from unittest.mock import patch 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 @@ async def test_load_failed_host_unavailable(aioclient_mock, hass): 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 | 71 ++++++- homeassistant/components/netgear/sensor.py | 209 ++++++++++++++++++- 4 files changed, 295 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/netgear/__init__.py b/homeassistant/components/netgear/__init__.py index 919cf25ae82..2842157f578 100644 --- a/homeassistant/components/netgear/__init__.py +++ b/homeassistant/components/netgear/__init__.py @@ -9,7 +9,13 @@ from homeassistant.exceptions import ConfigEntryNotReady 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 b1d5dd22942..02b78e164ac 100644 --- a/homeassistant/components/netgear/const.py +++ b/homeassistant/components/netgear/const.py @@ -9,6 +9,7 @@ CONF_CONSIDER_HOME = "consider_home" 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 d275aaf7fb2..b358939d122 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 @@ class NetgearRouter: 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 @@ class NetgearRouter: 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 @@ class NetgearRouter: 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,11 +154,15 @@ class NetgearRouter: async def async_get_attached_devices(self) -> list: """Get the devices connected to the router.""" if self.method_version == 1: - 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 + ) - return await self.hass.async_add_executor_job(self._api.get_attached_devices_2) + async with self._api_lock: + 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.""" @@ -186,6 +195,11 @@ class NetgearRouter: 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 @@ class NetgearDeviceEntity(NetgearBaseEntity): 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 0db0e4f19f4..16c63a8cdcb 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 @@ SENSOR_TYPES = { 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 @@ class NetgearSensorEntity(NetgearDeviceEntity, SensorEntity): 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 e9e1657065d..f44a59f18eb 100644 --- a/homeassistant/components/caldav/calendar.py +++ b/homeassistant/components/caldav/calendar.py @@ -232,7 +232,11 @@ class WebDavCalendarData: 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 32119b85597..b49aee29ba1 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 5c8a1c556c9..5e837eb36ac 100644 --- a/tests/components/recorder/test_migrate.py +++ b/tests/components/recorder/test_migrate.py @@ -5,7 +5,7 @@ import importlib 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 @@ async def test_schema_migrate(hass, start_version): 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 e137d0f673e..a551c6e3b9e 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 f4cb70f421a..e4aba5fbb24 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 f0e27283355..9afbf80297c 100644 --- a/homeassistant/components/github/config_flow.py +++ b/homeassistant/components/github/config_flow.py @@ -10,7 +10,6 @@ from aiogithubapi import ( GitHubException, GitHubLoginDeviceModel, GitHubLoginOauthModel, - GitHubRepositoryModel, ) from aiogithubapi.const import OAUTH_USER_LOGIN import voluptuous as vol @@ -34,11 +33,12 @@ from .const import ( ) -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 starred_repositories(hass: HomeAssistant, access_token: str) -> list[s 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class OptionsFlowHandler(config_entries.OptionsFlow): 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 7a23156759d..79e792aa7d8 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 1da5ead8fe0..25b544b591f 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 9a296d256ce..cbc1398208c 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 dad97472620..2bf0fac209f 100644 --- a/tests/components/github/test_config_flow.py +++ b/tests/components/github/test_config_flow.py @@ -4,7 +4,7 @@ from unittest.mock import AsyncMock, MagicMock, patch 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 2e9a8c05163..d0e27662d41 100644 --- a/homeassistant/components/button/__init__.py +++ b/homeassistant/components/button/__init__.py @@ -113,8 +113,9 @@ class ButtonEntity(RestoreEntity): 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 774aaad0ee4..846c0fbc7c6 100644 --- a/homeassistant/components/scene/__init__.py +++ b/homeassistant/components/scene/__init__.py @@ -113,8 +113,9 @@ class Scene(RestoreEntity): 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 b49aee29ba1..cb94018b1b1 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 50d56915dd4..5e7c2ef1938 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 f0e5ed2852f..67bbad0aceb 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 c346378fbc2..0ec37fdeb6f 100644 --- a/homeassistant/components/androidtv/config_flow.py +++ b/homeassistant/components/androidtv/config_flow.py @@ -124,6 +124,14 @@ class AndroidTVFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 37e0ae485c6..cd8e86a42a2 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 25b544b591f..52f3b1eb58b 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 cbc1398208c..946187fec5f 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 c92ac11ba4b..4411945c71b 100644 --- a/tests/components/androidtv/patchers.py +++ b/tests/components/androidtv/patchers.py @@ -185,3 +185,15 @@ PATCH_ANDROIDTV_UPDATE_EXCEPTION = patch( "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 5326e48f7b9..e97de0fc928 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 cb94018b1b1..b8f15a811db 100644 --- a/homeassistant/components/recorder/migration.py +++ b/homeassistant/components/recorder/migration.py @@ -6,6 +6,7 @@ import logging 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 f308595adbd..f055a5f32eb 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 a8a6ebc901e..640a4b4533b 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 31778781676..0b1b8f7d17f 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 03e99959fd4..f16666609bb 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 00000000000..dc26bca5512 --- /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 673e0ac4dfe..5f3cc285517 100644 --- a/homeassistant/components/sonos/__init__.py +++ b/homeassistant/components/sonos/__init__.py @@ -38,6 +38,7 @@ from .const import ( SONOS_CHECK_ACTIVITY, SONOS_REBOOTED, SONOS_SPEAKER_ACTIVITY, + SONOS_VANISHED, UPNP_ST, ) from .favorites import SonosFavorites @@ -274,14 +275,19 @@ class SonosDiscoveryManager: 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 94f2aa2b9f6..88f86034aea 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 8a9b13648e9..e29b27e9026 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 45a912fefec..c9f7d19a96a 100644 --- a/homeassistant/components/homewizard/config_flow.py +++ b/homeassistant/components/homewizard/config_flow.py @@ -10,7 +10,7 @@ import async_timeout 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """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 87a02a446e9..02e0b5c0c23 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 6154dd14bd2..84971ba89f1 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 @@ from .const import REDACTED 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 ca6eca9ea9f..7d5736a2e68 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 c18e8352b24..140fdfe9178 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 5a8bd4651c5..8ba16391d7e 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 cdaf5c73e74..02e54c9dd73 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -546,7 +546,7 @@ class KNXModule: 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 9c382fcb7fa..d60e0a10f3a 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 de69daefef6..91dc31d17f7 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 @@ def setup_services(hass: HomeAssistant) -> 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 9a4eb342f71..2c81ecbe183 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 @@ class BlockSleepingClimate( 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 30e98da7637..0c44ea52f07 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -24,7 +24,6 @@ import pathlib import re import threading from time import monotonic -from types import MappingProxyType from typing import ( TYPE_CHECKING, Any, @@ -83,6 +82,7 @@ from .util.async_ import ( 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 @@ class State: 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 @@ class State: "_", " " ) - 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 @@ class State: 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 @@ class StateMachine: 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 @@ class ServiceCall: """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 3c82639251a..5c2882ec2e2 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 00000000000..f9cc949afdc --- /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 3ea4cde2cec..3da0fcb98dd 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 c2d99967a4b..afb5f507044 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -39,6 +39,7 @@ from homeassistant.exceptions import ( 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 00000000000..7528c843f50 --- /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 813dcdd694f..f84a0e0a643 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 76aef3a035d..9d82d5c7655 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 0e90341ef03..1faba4734f5 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 7518a12d200..f49e83e9b2a 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 e9dbb39a609..bb60366aa86 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 fca625597d4..3d294a43d31 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 3ced95d27ff..a4421b566f6 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 975dacd5c2a..5c46b1b0282 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 2c314f33c2d..31af38ae14b 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 ac82268cef4..5230b14e192 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 873867c9819..971cfedd05d 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 8ac8a1a5b1d..17f85254208 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 b3943953621..d3263b9c849 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 f8299a59445..e96897ad1df 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 21a2a4f2159..0f3b63d4b45 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 6617d107471..00539cbcb70 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 f16666609bb..a3e2294e5ab 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 cb2573eedb8..b7391dbc823 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 00000000000..ec0979877cc --- /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 00000000000..dbe17adac00 --- /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 00000000000..30b3efb11d4 --- /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 00000000000..6efacfb8b95 --- /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 00000000000..ca50cd8c7e2 --- /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 00000000000..59e6d18c179 --- /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 00000000000..7d95281e14a --- /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 365aa903b09..c521bd85e3e 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -365,6 +365,7 @@ FLOWS = [ "wiffi", "wilight", "withings", + "wiz", "wled", "wolflink", "xbox", diff --git a/requirements_all.txt b/requirements_all.txt index 52f3b1eb58b..30f4abb1a34 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 946187fec5f..ab5bab95f90 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 00000000000..c2f67982b84 --- /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 00000000000..e08ca87a389 --- /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 ac4c8453327..040f4a128ee 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 e38671ae912..5d2070b9015 100644 --- a/homeassistant/components/tuya/const.py +++ b/homeassistant/components/tuya/const.py @@ -87,10 +87,12 @@ PLATFORMS = [ 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 5154afb9ab5..b0b7aeec2be 100644 --- a/homeassistant/components/tuya/select.py +++ b/homeassistant/components/tuya/select.py @@ -274,6 +274,24 @@ SELECTS: dict[str, tuple[SelectEntityDescription, ...]] = { 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 06c4a783066..e415d26ad4c 100644 --- a/homeassistant/components/tuya/sensor.py +++ b/homeassistant/components/tuya/sensor.py @@ -742,6 +742,67 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = { 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 @@ class TuyaSensorEntity(TuyaEntity, SensorEntity): 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 c1171f81fcb..ada9c528ac8 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 ff246817f61..a11aadba321 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 4559bdf3364..ae63830bd8d 100644 --- a/homeassistant/components/tuya/switch.py +++ b/homeassistant/components/tuya/switch.py @@ -198,6 +198,12 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { 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 212beddfae3..00000000000 --- 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 0c44ea52f07..a29067cdb0d 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -40,7 +40,7 @@ import attr 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_1_SHUTDOWN_TIMEOUT = 100 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 c2c92f06488..3231d34a342 100644 --- a/homeassistant/components/upnp/device.py +++ b/homeassistant/components/upnp/device.py @@ -12,7 +12,7 @@ from async_upnp_client.exceptions import UpnpError 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 @@ class 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 39a63893e33..bd2096b59a0 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 6465b542f36..4a4ffe6eb0a 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 30f4abb1a34..268bdad0ce7 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 ab5bab95f90..a2f2194c0a2 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 9a0566531c3..f5a8df0d652 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -449,6 +449,7 @@ class SimpliSafe: 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 bc0dddef47c..c7c03467c94 100644 --- a/homeassistant/components/simplisafe/diagnostics.py +++ b/homeassistant/components/simplisafe/diagnostics.py @@ -11,14 +11,28 @@ from homeassistant.core import HomeAssistant 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 d9e6d46c2eb..d4517717434 100644 --- a/tests/components/simplisafe/conftest.py +++ b/tests/components/simplisafe/conftest.py @@ -18,11 +18,12 @@ USER_ID = "12345" @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 d2c2866bf5b..13d5c778e89 100644 --- a/tests/components/simplisafe/test_diagnostics.py +++ b/tests/components/simplisafe/test_diagnostics.py @@ -7,7 +7,96 @@ 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": {}}, + "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 f5a8df0d652..a4ff2a618bc 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -4,7 +4,7 @@ from __future__ import annotations 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 @@ class SimpliSafe: 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 @@ class SimpliSafe: 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 @@ class SimpliSafe: 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 @@ class SimpliSafe: 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 @@ class SimpliSafe: """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 43bcaee2059..cf896c3a320 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 @@ class SimpliSafeAlarm(SimpliSafeEntity, AlarmControlPanelEntity): 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 44244e9c573..ad6e01f0422 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 @@ class SimpliSafeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 14816cdd579..1e7be48979b 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 @@ class SimpliSafeLock(SimpliSafeEntity, LockEntity): @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 2b879c30a16..38461bce859 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -9,7 +9,6 @@ import inspect 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 @@ class SensorEntity(Entity): 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 @@ class SensorEntity(Entity): 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 @@ class SensorEntity(Entity): 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 b49d8894932..eed88d92d04 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 a3e2294e5ab..003d148a702 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 c2814f86f06..00000000000 --- 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 3a5f4fb8daa..00000000000 --- 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 b3291e25617..00000000000 --- 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 268bdad0ce7..6ed8342b0e6 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 bf72d9edc31..e8bb0f3366a 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 @@ class SmileBinarySensor(SmileGateway): 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 @@ class PwBinarySensor(SmileBinarySensor, BinarySensorEntity): 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 b0b7aeec2be..a3e1d0439ae 100644 --- a/homeassistant/components/tuya/select.py +++ b/homeassistant/components/tuya/select.py @@ -247,12 +247,14 @@ SELECTS: dict[str, tuple[SelectEntityDescription, ...]] = { 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 e415d26ad4c..effb5a1443b 100644 --- a/homeassistant/components/tuya/sensor.py +++ b/homeassistant/components/tuya/sensor.py @@ -803,6 +803,16 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = { 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 ba332a08a16..8f9a18d1f11 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 17e6e967a34..12241a00513 100644 --- a/homeassistant/components/tuya/climate.py +++ b/homeassistant/components/tuya/climate.py @@ -73,6 +73,12 @@ CLIMATE_DESCRIPTIONS: dict[str, 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 4781250874f..ebdc40f6f12 100644 --- a/homeassistant/components/sensibo/coordinator.py +++ b/homeassistant/components/sensibo/coordinator.py @@ -68,7 +68,7 @@ class SensiboDataUpdateCoordinator(DataUpdateCoordinator): 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 ebdc40f6f12..9509be88266 100644 --- a/homeassistant/components/sensibo/coordinator.py +++ b/homeassistant/components/sensibo/coordinator.py @@ -107,5 +107,6 @@ class SensiboDataUpdateCoordinator(DataUpdateCoordinator): "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 1950b00a079..ae4eb460c6c 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 6ed8342b0e6..b01acde1717 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 a2f2194c0a2..b8595653a36 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 f060bf8d8c6..21e2ed7c94d 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 b01acde1717..8293181d523 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 b8595653a36..8de02cc3a64 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 1209dff7495..07fa93d976c 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 dea3fcafcde..e04385dcf3d 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 8293181d523..08f69d1a9dc 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 14be2d1a796..bd988b50e3a 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 26dc9f65bc2..1dac4ddd78a 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 08f69d1a9dc..f2f1e67bd17 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 8de02cc3a64..786c4683b71 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 9c90067efef..c74860c7f64 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 f2f1e67bd17..bcada9af42b 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 786c4683b71..3dad22474db 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 fe451db44b8..a8be4e4fcdb 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 5f624ea8e1c..2363b124e43 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 4f19e6afae2..d1be59ebc87 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 cb1f3a176a4..259b3ec3b7b 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 a49a471038c..db8e57673b1 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 817bdaccd3c..92baec1e42b 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 771d6f6fd9d..6d8f50c81bf 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 3f0b9516ead..5cded6a179d 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 bcada9af42b..a886a0f98b3 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 3dad22474db..74b3e53e8c8 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 72716f15dfd..f16a65fb4c0 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 bd511d84eb5..d61c21972fb 100644 --- a/homeassistant/components/apple_tv/__init__.py +++ b/homeassistant/components/apple_tv/__init__.py @@ -242,7 +242,7 @@ class AppleTVManager: 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 69567bf65fc..7ee804f42f1 100644 --- a/homeassistant/components/climacell/const.py +++ b/homeassistant/components/climacell/const.py @@ -260,7 +260,7 @@ CC_SENSOR_TYPES = ( 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 @@ CC_SENSOR_TYPES = ( 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 @@ CC_V3_SENSOR_TYPES = ( 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 @@ CC_V3_SENSOR_TYPES = ( 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 b21b75be9b5..2e931d835a8 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 86c02ae9ee9..686a7c13e58 100644 --- a/homeassistant/components/cpuspeed/sensor.py +++ b/homeassistant/components/cpuspeed/sensor.py @@ -75,7 +75,7 @@ class CPUSpeedSensor(SensorEntity): 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 @@ class CPUSpeedSensor(SensorEntity): } 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 87bcd685a1f..e48f117648a 100644 --- a/homeassistant/components/enocean/sensor.py +++ b/homeassistant/components/enocean/sensor.py @@ -168,7 +168,7 @@ class EnOceanPowerSensor(EnOceanSensor): # 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 b86b6844551..fc788e88d72 100644 --- a/homeassistant/components/enocean/switch.py +++ b/homeassistant/components/enocean/switch.py @@ -91,7 +91,7 @@ class EnOceanSwitch(EnOceanEntity, SwitchEntity): 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 1063dfda08a..f74dc7690ca 100644 --- a/homeassistant/components/fail2ban/sensor.py +++ b/homeassistant/components/fail2ban/sensor.py @@ -65,7 +65,7 @@ class BanSensor(SensorEntity): 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 fec760fbe7a..59cb75901a2 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 a25ae1b4660..e5a8f1424c2 100644 --- a/homeassistant/components/glances/const.py +++ b/homeassistant/components/glances/const.py @@ -19,7 +19,7 @@ DEFAULT_SCAN_INTERVAL = 60 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 b28ccd86b84..a907dd1695a 100644 --- a/homeassistant/components/glances/sensor.py +++ b/homeassistant/components/glances/sensor.py @@ -129,14 +129,14 @@ class GlancesSensor(SensorEntity): 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 @@ class GlancesSensor(SensorEntity): 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 @@ class GlancesSensor(SensorEntity): 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 5b3a2db0b80..20191c61668 100644 --- a/homeassistant/components/google_assistant/trait.py +++ b/homeassistant/components/google_assistant/trait.py @@ -774,14 +774,10 @@ class StartStopTrait(_Trait): """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 d8d5a16ac50..922c4c52568 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 9e77563f7a2..bb168fce09f 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -59,7 +59,7 @@ DEFAULT_DEVELOPMENT: Final = "0" 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 c95c96c505b..7a6248254d8 100644 --- a/homeassistant/components/integration/sensor.py +++ b/homeassistant/components/integration/sensor.py @@ -49,7 +49,7 @@ RIGHT_METHOD = "right" 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 af475e9c380..c118e56f9e3 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 de607f2a3c0..c27c4f43920 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 42cacfc8ab5..9e5bd6e4ac9 100644 --- a/homeassistant/components/nzbget/sensor.py +++ b/homeassistant/components/nzbget/sensor.py @@ -126,7 +126,7 @@ class NZBGetSensor(NZBGetEntity, SensorEntity): 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 45e8f55a790..7866b99221e 100644 --- a/homeassistant/components/pandora/media_player.py +++ b/homeassistant/components/pandora/media_player.py @@ -253,7 +253,7 @@ class PandoraMediaPlayer(MediaPlayerEntity): 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 65c33a41ff3..e4bbf7df3d2 100644 --- a/homeassistant/components/pyload/sensor.py +++ b/homeassistant/components/pyload/sensor.py @@ -132,7 +132,7 @@ class PyLoadSensor(SensorEntity): 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 8911927d732..91e1eeb3257 100644 --- a/homeassistant/components/sonarr/sensor.py +++ b/homeassistant/components/sonarr/sensor.py @@ -167,8 +167,8 @@ class SonarrSensor(SonarrEntity, SensorEntity): 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 @@ class SonarrSensor(SonarrEntity, SensorEntity): 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 735c656134b..e2455ad63df 100644 --- a/homeassistant/components/speedtestdotnet/const.py +++ b/homeassistant/components/speedtestdotnet/const.py @@ -36,14 +36,14 @@ SENSOR_TYPES: Final[tuple[SpeedtestSensorEntityDescription, ...]] = ( 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 dc87116914a..042fc33060a 100644 --- a/homeassistant/components/startca/sensor.py +++ b/homeassistant/components/startca/sensor.py @@ -194,7 +194,7 @@ class StartcaData: :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 305e6dda4e7..18014de8c7a 100644 --- a/homeassistant/components/synology_dsm/sensor.py +++ b/homeassistant/components/synology_dsm/sensor.py @@ -105,7 +105,7 @@ class SynoDSMUtilSensor(SynoDSMSensor): # 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 @@ class SynoDSMStorageSensor(SynologyDSMDeviceEntity, SynoDSMSensor): # 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 9a4f0cc0aa8..c4969e2c14c 100644 --- a/homeassistant/components/system_bridge/sensor.py +++ b/homeassistant/components/system_bridge/sensor.py @@ -104,7 +104,7 @@ BASE_SENSOR_TYPES: tuple[SystemBridgeSensorEntityDescription, ...] = ( 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 @@ BASE_SENSOR_TYPES: tuple[SystemBridgeSensorEntityDescription, ...] = ( 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 bceda1b453a..7a57c06ef58 100644 --- a/homeassistant/components/systemmonitor/sensor.py +++ b/homeassistant/components/systemmonitor/sensor.py @@ -51,7 +51,7 @@ _LOGGER = logging.getLogger(__name__) 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 15e57f223e9..22764f81080 100644 --- a/homeassistant/components/tuya/base.py +++ b/homeassistant/components/tuya/base.py @@ -45,11 +45,11 @@ class IntegerTypeData: 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 8a3b9f046ce..499233717b6 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 65de5fd04cc..b892fc9a67f 100644 --- a/homeassistant/components/zha/climate.py +++ b/homeassistant/components/zha/climate.py @@ -431,14 +431,10 @@ class Thermostat(ZhaEntity, ClimateEntity): 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 a29067cdb0d..27906e0401f 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -1919,7 +1919,7 @@ def _async_create_timer(hass: HomeAssistant) -> 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 f74aec0efe8..281ab3108b6 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 916d203782a..d371017a475 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 1005b48e1ca..c57d082de61 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 @@ async def time_changed_helper(hass): 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 @@ async def state_changed_helper(hass): nonlocal count count += 1 - if count == 10 ** 6: + if count == 10**6: event.set() for idx in range(1000): @@ -173,7 +173,7 @@ async def state_changed_helper(hass): "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 b967a6a0b1e..4e76fa32de3 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 2e37b2175ba..ca7828267b6 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 ba8914c3b1d..497e8b36e99 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 d958c633b62..e0cc9b715c1 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 41e9bc50038..93dd1b87b44 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 31a8a5c71e3..6868ff1f71d 100644 --- a/tests/test_util/aiohttp.py +++ b/tests/test_util/aiohttp.py @@ -21,7 +21,7 @@ RETYPE = type(re.compile("")) 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 0b1b8f7d17f..e9ab935f6ab 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 d8851868719..af7f43eae4d 100644 --- a/tests/util/test_json.py +++ b/tests/util/test_json.py @@ -143,21 +143,15 @@ def test_find_unserializable_data(): 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 test_find_unserializable_data(): 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 1c13d1b3684..fe118c79dbd 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 9307cb3f98f..2fdfd952d8e 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.const import ( ) 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 @@ class SmileSensor(SmileGateway, SensorEntity): 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 @@ class PwThermostatSensor(SmileSensor): 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 @@ class PwThermostatSensor(SmileSensor): 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 @@ class PwAuxDeviceSensor(SmileSensor): 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 @@ class PwAuxDeviceSensor(SmileSensor): 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 @@ class PwAuxDeviceSensor(SmileSensor): 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 @@ class PwPowerSensor(SmileSensor): 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 451ea70edab..c0c0db85cf3 100644 --- a/homeassistant/components/vicare/climate.py +++ b/homeassistant/components/vicare/climate.py @@ -95,12 +95,6 @@ HA_TO_VICARE_PRESET_HEATING = { } -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 42594ec202e..23096cfeacd 100644 --- a/homeassistant/components/vicare/sensor.py +++ b/homeassistant/components/vicare/sensor.py @@ -43,46 +43,6 @@ from .const import ( _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 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( 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 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( 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 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( 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 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( 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 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( 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 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( 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 @@ CIRCUIT_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( 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 @@ BURNER_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( 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 0107ff8fe4c..8a7169333bf 100644 --- a/homeassistant/components/vicare/water_heater.py +++ b/homeassistant/components/vicare/water_heater.py @@ -56,18 +56,6 @@ HA_TO_VICARE_HVAC_DHW = { } -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 6c516c09d83..c118093f2e1 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 c118093f2e1..2bc65934477 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 | 35 +- homeassistant/components/wiz/const.py | 9 + homeassistant/components/wiz/light.py | 401 +++++++----------- 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 | 78 ++-- 14 files changed, 312 insertions(+), 337 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 e84e01b1803..f2c0c29ef27 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 ec0979877cc..6e7f841ebb2 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 dbe17adac00..efa9cc1443e 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 + 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=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 30b3efb11d4..96a96e662f1 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 6efacfb8b95..a8fcf1e1dae 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 @@ from homeassistant.components.light import ( 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 -) +DEFAULT_COLOR_MODES = {COLOR_MODE_HS, COLOR_MODE_COLOR_TEMP} +DEFAULT_MIN_MIREDS = 153 +DEFAULT_MAX_MIREDS = 454 -# set poll interval to 15 sec because of changes from external to the bulb -SCAN_INTERVAL = timedelta(seconds=15) +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 -async def async_setup_entry(hass, entry, async_add_entities): +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 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): + def is_on(self) -> bool | None: """Return true if light is on.""" - return self._state + is_on: bool | None = self._light.status + return is_on - async def async_turn_on(self, **kwargs): + @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.""" + 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) -> 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 @@ -150,199 +245,9 @@ class WizBulbEntity(LightEntity): ) 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 ca50cd8c7e2..8b366e7f506 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 00000000000..efbb2a664b1 --- /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 59e6d18c179..3088a7f098d 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 7d95281e14a..24f34bd0f5b 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 00000000000..edce7d47fea --- /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 32ee9352326..524ef2c5420 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 a886a0f98b3..513a69e59b4 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 74b3e53e8c8..f098beb6261 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 e08ca87a389..20afd994046 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 @@ from homeassistant.components.wiz.config_flow import ( 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"} +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"} -TEST_CONNECTION = {CONF_HOST: "1.1.1.1", CONF_NAME: "Test Bulb"} +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 -TEST_NO_IP = {CONF_HOST: "this is no IP input", CONF_NAME: "Test Bulb"} + 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 30e766b6ec4..085b3ad733d 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 513a69e59b4..bfc7f30cfb2 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 f098beb6261..c81f4943114 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 a120daf0083..eb0c56115c8 100644 --- a/homeassistant/components/plugwise/config_flow.py +++ b/homeassistant/components/plugwise/config_flow.py @@ -15,17 +15,14 @@ from homeassistant.const import ( 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 @@ class PlugwiseConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 9c6823e22e4..772bccd92c1 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 @@ SCHEDULE_ON = "true" SMILE = "smile" STRETCH = "stretch" STRETCH_USERNAME = "stretch" -UNDO_UPDATE_LISTENER = "undo_update_listener" UNIT_LUMEN = "lm" PLATFORMS_GATEWAY = [ @@ -47,9 +48,9 @@ DEFAULT_MIN_TEMP = 4 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 67f1895943d..24c937799ba 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 @@ from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, CONF_PORT, - CONF_SCAN_INTERVAL, CONF_USERNAME, ) from homeassistant.core import HomeAssistant, callback @@ -36,7 +34,6 @@ from homeassistant.helpers.update_coordinator import ( ) from .const import ( - API, COORDINATOR, DEFAULT_PORT, DEFAULT_SCAN_INTERVAL, @@ -47,7 +44,6 @@ from .const import ( 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_setup_entry_gw(hass: HomeAssistant, entry: ConfigEntry) -> bool: _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_setup_entry_gw(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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_setup_entry_gw(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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 9f9be299f84..284be67a386 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 import zeroconf from homeassistant.components.plugwise.const import ( API, DEFAULT_PORT, - DEFAULT_SCAN_INTERVAL, DOMAIN, FLOW_NET, FLOW_TYPE, @@ -24,7 +23,6 @@ from homeassistant.const import ( 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 b7391dbc823..b89de457751 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 494cae37b57..2d2b01cb04b 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 | 227 +++++++------------ 1 file changed, 82 insertions(+), 145 deletions(-) diff --git a/homeassistant/components/plugwise/climate.py b/homeassistant/components/plugwise/climate.py index 86f701ccb7d..600b6184191 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.config_entries import ConfigEntry 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 @@ from .gateway import SmileGateway 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"] + # 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 - 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"] + # 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 + + # 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 + + # 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._compressor_state = heater_central_data["compressor_state"] + 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 - self._hvac_mode = HVAC_MODE_HEAT - if self._compressor_state is not None: - self._hvac_mode = HVAC_MODE_HEAT_COOL - - if self._schema_status: - self._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 670455d7354..2fb3729d0a8 100644 --- a/homeassistant/components/saj/sensor.py +++ b/homeassistant/components/saj/sensor.py @@ -20,7 +20,6 @@ from homeassistant.const import ( CONF_TYPE, CONF_USERNAME, ENERGY_KILO_WATT_HOUR, - EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, MASS_KILOGRAMS, POWER_WATT, @@ -33,6 +32,7 @@ from homeassistant.exceptions import PlatformNotReady 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_setup_platform( 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 a5d0ca736fc..0af31b47730 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -117,6 +117,7 @@ DISCOVERY_SOURCES = ( 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 003d148a702..6b0de56b35d 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 6e7f841ebb2..c45925adec7 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 @@ PLATFORMS = [Platform.LIGHT] 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 efa9cc1443e..34b484f145e 100644 --- a/homeassistant/components/wiz/config_flow.py +++ b/homeassistant/components/wiz/config_flow.py @@ -5,15 +5,17 @@ import logging 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): _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 96a96e662f1..a88c445614a 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 00000000000..c7ee612c6a9 --- /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 a8fcf1e1dae..09e17976eb7 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 @@ 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 + 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 8b366e7f506..0842fd967b0 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 3088a7f098d..99f491e7360 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 24f34bd0f5b..b0d65e9f957 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 edce7d47fea..ceaab797d5e 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 701117cb562..2e9672f99eb 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -608,6 +608,14 @@ DHCP = [ "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 20afd994046..18c28f50c0e 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 @@ FAKE_BULB_CONFIG = { "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 4310f8f2caf..dd247c4cab9 100644 --- a/homeassistant/components/dhcp/__init__.py +++ b/homeassistant/components/dhcp/__init__.py @@ -179,6 +179,7 @@ class WatcherBase: 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 @@ class WatcherBase: 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 fb3387aeab6..0956230d787 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 f055a5f32eb..21c877f9377 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 e9ab935f6ab..b77540acc2b 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 23096cfeacd..a0d7208f1b2 100644 --- a/homeassistant/components/vicare/sensor.py +++ b/homeassistant/components/vicare/sensor.py @@ -58,6 +58,7 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( 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 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( 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 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( 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 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( 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 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( 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 @@ CIRCUIT_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( 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 @@ BURNER_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( 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 @@ BURNER_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( 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 @@ BURNER_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( icon="mdi:percent", native_unit_of_measurement=PERCENTAGE, value_getter=lambda api: api.getModulation(), + state_class=SensorStateClass.MEASUREMENT, ), ) @@ -231,6 +241,7 @@ COMPRESSOR_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( 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 @@ COMPRESSOR_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( 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 @@ COMPRESSOR_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( 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 @@ COMPRESSOR_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( 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 @@ COMPRESSOR_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( 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 @@ COMPRESSOR_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( 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 @@ COMPRESSOR_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( 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 | 208 +++++------------- 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, 141 insertions(+), 153 deletions(-) create mode 100644 homeassistant/components/wiz/entity.py create mode 100644 homeassistant/components/wiz/switch.py diff --git a/.coveragerc b/.coveragerc index 6b0de56b35d..8babaab4e25 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 c45925adec7..ddf324278cf 100644 --- a/homeassistant/components/wiz/__init__.py +++ b/homeassistant/components/wiz/__init__.py @@ -22,7 +22,7 @@ from .models import WizData _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 00000000000..0c4829383d3 --- /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 09e17976eb7..ee586c8939d 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 @@ from homeassistant.components.light import ( 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 @@ from homeassistant.components.light import ( 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 + 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() - @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 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]) + color_temp = None + if ATTR_COLOR_TEMP in kwargs: + color_temp = color_temperature_mired_to_kelvin(kwargs[ATTR_COLOR_TEMP]) - 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, - ) + 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() - sceneid = None - if ATTR_EFFECT in kwargs: - sceneid = get_id_from_scene_name(kwargs[ATTR_EFFECT]) + _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) - 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() + 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 99f491e7360..2195bb09a03 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 00000000000..eae7e3d47a8 --- /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 b0d65e9f957..192d1137c2f 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 18c28f50c0e..a043d4a654e 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 eb0c56115c8..e611199ccf6 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 @@ from homeassistant.const import ( 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 @@ from .const import ( DEFAULT_PORT, DEFAULT_USERNAME, DOMAIN, - FLOW_NET, FLOW_SMILE, FLOW_STRETCH, - FLOW_TYPE, - FLOW_USB, PW_TYPE, SMILE, STRETCH, @@ -39,14 +38,6 @@ from .const import ( _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 @@ class PlugwiseConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): CONF_PORT: discovery_info.port, CONF_USERNAME: self._username, } - return await self.async_step_user_gateway() + return await self.async_step_user() - # PLACEHOLDER USB 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 @@ class PlugwiseConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 ) @@ -163,30 +138,7 @@ class PlugwiseConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_create_entry(title=api.smile_name, data=user_input) return self.async_show_form( - step_id="user_gateway", + step_id="user", 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, - 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 772bccd92c1..cd31255a040 100644 --- a/homeassistant/components/plugwise/const.py +++ b/homeassistant/components/plugwise/const.py @@ -8,11 +8,9 @@ ATTR_ILLUMINANCE = "illuminance" 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 06f9b56e689..65415285de2 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 284be67a386..3e9ce985a5c 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 @@ from plugwise.exceptions import ( ) 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 @@ from homeassistant.const import ( 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_PORT = 81 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 @@ TEST_DISCOVERY = zeroconf.ZeroconfServiceInfo( }, 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" + assert result.get("type") == RESULT_TYPE_FORM + assert result.get("errors") == {} + assert result.get("step_id") == "user" + assert "flow_id" in result - result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={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"] == {} - assert result["step_id"] == "user_gateway" - - -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} - ) - 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" + assert len(mock_setup_entry.mock_calls) == 0 + assert len(mock_smile_config_flow.connect.mock_calls) == 1 -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" - - 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"} + 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, + } - -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 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 e8bb0f3366a..25b1578cd8a 100644 --- a/homeassistant/components/plugwise/binary_sensor.py +++ b/homeassistant/components/plugwise/binary_sensor.py @@ -19,7 +19,7 @@ from .const import ( 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 600b6184191..e3185c0701d 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -30,7 +30,7 @@ from .const import ( 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 00000000000..ed9f2c61a78 --- /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 24c937799ba..9d453596450 100644 --- a/homeassistant/components/plugwise/gateway.py +++ b/homeassistant/components/plugwise/gateway.py @@ -13,25 +13,12 @@ from plugwise.exceptions import ( 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 2fdfd952d8e..da09d4afa13 100644 --- a/homeassistant/components/plugwise/sensor.py +++ b/homeassistant/components/plugwise/sensor.py @@ -37,7 +37,7 @@ from .const import ( 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 1aa8bdc51d7..baefcaeb710 100644 --- a/homeassistant/components/plugwise/switch.py +++ b/homeassistant/components/plugwise/switch.py @@ -9,7 +9,7 @@ from homeassistant.core import HomeAssistant, callback 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 5d2070b9015..d976bee2792 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 d234b6778be..d9cde61a276 100644 --- a/homeassistant/components/tuya/number.py +++ b/homeassistant/components/tuya/number.py @@ -250,6 +250,20 @@ NUMBERS: dict[str, tuple[NumberEntityDescription, ...]] = { 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 a3e1d0439ae..d9103b916f4 100644 --- a/homeassistant/components/tuya/select.py +++ b/homeassistant/components/tuya/select.py @@ -276,6 +276,45 @@ SELECTS: dict[str, tuple[SelectEntityDescription, ...]] = { 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 effb5a1443b..7fcea1095d4 100644 --- a/homeassistant/components/tuya/sensor.py +++ b/homeassistant/components/tuya/sensor.py @@ -742,6 +742,34 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = { 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 ada9c528ac8..a765912d036 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 ae63830bd8d..fc3d8d86baf 100644 --- a/homeassistant/components/tuya/switch.py +++ b/homeassistant/components/tuya/switch.py @@ -558,6 +558,28 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { 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 9d453596450..611a9153cad 100644 --- a/homeassistant/components/plugwise/gateway.py +++ b/homeassistant/components/plugwise/gateway.py @@ -39,7 +39,6 @@ _LOGGER = logging.getLogger(__name__) 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_setup_entry_gw(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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_setup_entry_gw(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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 34b484f145e..9b753284dc5 100644 --- a/homeassistant/components/wiz/config_flow.py +++ b/homeassistant/components/wiz/config_flow.py @@ -14,11 +14,14 @@ from homeassistant.components import dhcp 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 0c4829383d3..de0b0e3f947 100644 --- a/homeassistant/components/wiz/entity.py +++ b/homeassistant/components/wiz/entity.py @@ -33,9 +33,14 @@ class WizToggleEntity(CoordinatorEntity, ToggleEntity): @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 ee586c8939d..bc5ac078ec7 100644 --- a/homeassistant/components/wiz/light.py +++ b/homeassistant/components/wiz/light.py @@ -5,7 +5,7 @@ import logging 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 @@ from .models import WizData _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 @@ class WizBulbEntity(WizToggleEntity, LightEntity): """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 @@ class WizBulbEntity(WizToggleEntity, LightEntity): 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 2195bb09a03..288fd76acc4 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 eae7e3d47a8..e6e34c73c3b 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 192d1137c2f..c8ee3d6df2e 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 a043d4a654e..a52ca323830 100644 --- a/tests/components/wiz/test_config_flow.py +++ b/tests/components/wiz/test_config_flow.py @@ -4,13 +4,12 @@ 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 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 _patch_wizlight(device=None, extended_white_range=None): 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 7610e76b7bb..896309f2593 100644 --- a/homeassistant/components/system_bridge/coordinator.py +++ b/homeassistant/components/system_bridge/coordinator.py @@ -63,16 +63,20 @@ class SystemBridgeDataUpdateCoordinator(DataUpdateCoordinator[Bridge]): 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 31c19e4614f..8fba9dd30cf 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 bfc7f30cfb2..88b2b92409a 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 c81f4943114..d9397b88cf0 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 25b1578cd8a..d0bfe0a2fe7 100644 --- a/homeassistant/components/plugwise/binary_sensor.py +++ b/homeassistant/components/plugwise/binary_sensor.py @@ -92,6 +92,7 @@ class SmileBinarySensor(PlugwiseEntity, BinarySensorEntity): 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 @@ class SmileBinarySensor(PlugwiseEntity, BinarySensorEntity): 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 e3185c0701d..cba9b6a095a 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -99,6 +99,7 @@ class PwThermostat(PlugwiseEntity, ClimateEntity): """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 @@ class PwThermostat(PlugwiseEntity, ClimateEntity): 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 ed9f2c61a78..8a2526d4811 100644 --- a/homeassistant/components/plugwise/entity.py +++ b/homeassistant/components/plugwise/entity.py @@ -22,6 +22,8 @@ from .const import DOMAIN 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 @@ class PlugwiseEntity(CoordinatorEntity): 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 da09d4afa13..642a7a5e1e6 100644 --- a/homeassistant/components/plugwise/sensor.py +++ b/homeassistant/components/plugwise/sensor.py @@ -313,7 +313,9 @@ class SmileSensor(PlugwiseEntity, SensorEntity): ) -> 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 @@ class SmileSensor(PlugwiseEntity, SensorEntity): 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 baefcaeb710..161728a9474 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 @@ class GwSwitch(PlugwiseEntity, SwitchEntity): 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 d0bfe0a2fe7..ca65a66b4da 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 @@ from .const import ( FLOW_OFF_ICON, FLOW_ON_ICON, IDLE_ICON, + LOGGER, NO_NOTIFICATION_ICON, NOTIFICATION_ICON, ) @@ -27,8 +26,6 @@ BINARY_SENSOR_MAP = { } 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 cba9b6a095a..1063254299e 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 @@ from .const import ( DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, DOMAIN, + LOGGER, SCHEDULE_OFF, SCHEDULE_ON, ) @@ -35,8 +35,6 @@ 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] -_LOGGER = logging.getLogger(__name__) - async def async_setup_entry( hass: HomeAssistant, @@ -119,9 +117,9 @@ class PwThermostat(PlugwiseEntity, ClimateEntity): 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 @@ class PwThermostat(PlugwiseEntity, ClimateEntity): ) 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 @@ class PwThermostat(PlugwiseEntity, ClimateEntity): 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 @@ class PwThermostat(PlugwiseEntity, ClimateEntity): 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 e611199ccf6..2ce8a686c8b 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 @@ from .const import ( DOMAIN, FLOW_SMILE, FLOW_STRETCH, + LOGGER, PW_TYPE, SMILE, STRETCH, @@ -36,8 +36,6 @@ from .const import ( ZEROCONF_MAP, ) -_LOGGER = logging.getLogger(__name__) - def _base_gw_schema(discovery_info): """Generate base schema for gateways.""" @@ -126,7 +124,7 @@ class PlugwiseConfigFlow(ConfigFlow, domain=DOMAIN): 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 cd31255a040..a885a047f7a 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 611a9153cad..e851941cf27 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 @@ from .const import ( 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_setup_entry_gw(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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 642a7a5e1e6..9d3f2d780b1 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 @@ from .const import ( DOMAIN, FLAME_ICON, IDLE_ICON, + LOGGER, SENSOR_MAP_DEVICE_CLASS, SENSOR_MAP_MODEL, SENSOR_MAP_STATE_CLASS, @@ -39,8 +38,6 @@ from .const import ( ) from .entity import PlugwiseEntity -_LOGGER = logging.getLogger(__name__) - ATTR_TEMPERATURE = [ "Temperature", TEMP_CELSIUS, @@ -350,7 +347,7 @@ class PwThermostatSensor(SmileSensor): 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 @@ class PwAuxDeviceSensor(SmileSensor): 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 @@ class PwPowerSensor(SmileSensor): 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 161728a9474..c862983119a 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.config_entries import ConfigEntry 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 @@ class GwSwitch(PlugwiseEntity, SwitchEntity): 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 @@ class GwSwitch(PlugwiseEntity, SwitchEntity): 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 73de35987ec..a230208a04e 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 88b2b92409a..39b2f6a8378 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 d9397b88cf0..9f89b0faa50 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 ce575277dad..7a81d414e2f 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 f9c2dbf2c91..42b014d9ed3 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 14a2f350f88..024d61ad8d4 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 1ab13f21d96..619dcfa9153 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 58431a4d313..6fae2369fd3 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 4bb3fc0fc09..5367a2e0c86 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 20c22238c12..c294fa7c86b 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 350f7f9865c..789906c36e4 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 e2b2ea2b4b0..0b58b36014a 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 00000000000..eec0a7cb383 --- /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 3eda86b60cb..6b838983637 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 00000000000..c0011b9e240 --- /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 8ad46475a71..9c1644382e5 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 08d797fd113..46fecc87e2c 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 5f5ef326485..a7aa5386417 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 cb7723d297b..13a43362f4a 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 606c529facc..3148940ef05 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 00000000000..e6c6e3e686e --- /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 598c747d87b..7d42d825345 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 b7fc1d36b52..907bb2b0d76 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 78e103f8b69..5d07abe3507 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 b30be708065..0012898808c 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 aecb2ee553f..381078f6d8d 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 fcbf8246205..8d71dd3070e 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 4b2e6a1fc65..6b23c26b2b2 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 6528bdb5a61..99de016dace 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 5eaa736cd5c..2f023dc3050 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 31af38ae14b..c576116efb2 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 00000000000..3210d36f1c8 --- /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 00000000000..8e1e8eae41d --- /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 00000000000..896c5b50791 --- /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 00000000000..34c3a0340b6 --- /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 8bd1aa046be..4ac528bc7ef 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 00000000000..ebb51349049 --- /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 571ba01a0ac..50724b72c58 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 c9eb2c5db37..fd575836741 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 00000000000..18ef9d6890a --- /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 a9437c09ba4..1707156c71a 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 00000000000..8471b3b5f11 --- /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 569372f2579..dd01e64ac64 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 b8d85b6960b..0614fd18b68 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 fd43f179032..c6bb137e571 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 aecb2ee553f..57cf824fd44 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 b6d105dc21a..ed7ca36d8c2 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 00000000000..788a8c5a11d --- /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 55ab5b1d17d..7fdd2f6c2c5 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 6084975c60e..e76e50d1caf 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 54900940d9b..36320b28606 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 b758a17357f..ea468429924 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 00000000000..eeceb373ce8 --- /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 32140df6fa7..472765dfba5 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 251383c2115..d00e805ad1d 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 28e9033bc11..ea7fe194fef 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 00000000000..f5d8a4d53a0 --- /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 9724aa87b12..98eaae46a3f 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 e041741e992..a1cdfb27918 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 00000000000..8fc295ebf9a --- /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 00000000000..e5fc7179f86 --- /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 4f0bc6628e3..eea8c3495b4 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 19873f86a31..385d9d8973d 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 8c6a8c07fdf..29fa43b4a16 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 b0c39d2c597..845e6949c01 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 00000000000..a73e4283fb1 --- /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 d7b4acd20c1..b507bda970d 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 c71df1f9b4e..640cf82fba1 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 2b5744077e1..59a86b9dcef 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 00000000000..26e4eed8900 --- /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 971cfedd05d..370bbd900c2 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 67273a20482..2f13daba62c 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 00000000000..e29ff8dee5f --- /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 945b9cfa6a8..676e0e9ca89 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 00000000000..dd95b5e28d7 --- /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 11f543797fe..049ed9ce79e 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 76192bb3427..10e5ee24ffd 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 d5432d5d60e..11833024f9c 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 85fc79981ce..bd62f226b2e 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 b81e93e09fd..e9e8ad9bf5a 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 00000000000..8b1e7f9d282 --- /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 c53bbe573ec..be7078143b0 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 87b78e719d0..a59a3efc089 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 1f842e20cf6..4eac27f1b1a 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 4e3e9483945..f8c2e32c5df 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 e6f90beed31..cbc8f9fb354 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 7850ff1c34d..204ada67d7c 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 dad23eecdcf..551ff120de7 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 00000000000..8a43dab9681 --- /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 1fa97f42105..5cec565d287 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 9838417fb49..bc8b4094337 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 9a899257add..c87fb65f446 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 35cb5786fd2..3ba3fdb4330 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 5900d42cba9..f7da38143ea 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 00000000000..9970910c04d --- /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 29a6b53d550..cb962803136 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 00000000000..1a4a2627af3 --- /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 00000000000..20cabc1bbe7 --- /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 f0d2561e4ee..78b2a8f8088 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 d71bea91ddb..4d7c3f964d4 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 0f69f0e96c9..539792754bc 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 319a3994459..03e8b3f1513 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 00000000000..192b190cf37 --- /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 402d66a0d55..6bf5e8416b0 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 805c15c252a..35f0bd1867e 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 49156d1cbc2..b61c256ef18 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 feee649656d..f4ccdf8b400 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 9879388d0d8..e19942a36af 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 f8d2ba4acb0..b25fbd9f5df 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 17846d1760d..2727efd1a08 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 0aadad14443..f49061c90aa 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 f8c23abd578..effe871645a 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 29c34000089..65d4bdeca80 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 e32b8ebcd3c..aed86dfe4ce 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 681ae04107a..d5ecb8c52ab 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 ffe5ddd2c99..01daca76a11 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 00000000000..0f034693986 --- /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 4057f75c1ea..fda8003d3e7 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 b8e8beeb094..eaf2621ff14 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 aecb2ee553f..9f791196b75 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 a3847cc08f8..a73ad869c2c 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 5472b660388..6c58f72eef7 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 b9f2b180b4b..138602fb472 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 e9aa45f91f2..cb4f5010017 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 00000000000..1b7397feadd --- /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 43dcd322834..fcd56ed621e 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 1109812260d..a813fc3ede2 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 3cfce894ca2..f4d134979ce 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 00000000000..e2c4495a77e --- /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 5c7ba4adb9d..0a551fc8874 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 2dbb86c6dd4..2336b3ded4f 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 6767d5d4d68..e31b6d6443c 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 00000000000..505788115c9 --- /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 00000000000..829a2b0a048 --- /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 00000000000..81d140f3d4f --- /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 c8ee3d6df2e..5747182a231 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 00000000000..9de0612b2f7 --- /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 00000000000..e417002cb90 --- /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 00000000000..c0108fc32c1 --- /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 00000000000..92d0c3f84d3 --- /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 00000000000..df4c08995f3 --- /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 35de8dfdcdc..9bfe5cc02a0 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 4064892a1fb..d752c023c72 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 47716969d47..3f6dcb8b015 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 6a8ad33c53b..3d46faee2eb 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 00000000000..68bca5c452f --- /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 19e12f287ff..13dac9bdf42 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 00539cbcb70..2cf46d54165 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 f2c0c29ef27..efdccdb1a43 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 4d97e8c5fac..ac7a9e38565 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 49af4740123..7aed3849ce8 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 dff0f7d53c6..e3b8b97aa8f 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 @@ ERROR_UNKNOWN = "unknown" _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 @@ class RokuConfigFlow(ConfigFlow, domain=DOMAIN): 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 261d0c95445..ef969b846aa 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 @@ class RokuEntity(CoordinatorEntity): 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 6d7989e93cb..619672e8f1f 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 67abae262d5..d6ad5c9cd13 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 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity): @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 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity): @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 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity): @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 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity): @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 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity): 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 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity): 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 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity): 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 8f0d39ed1d9..96c04597d89 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 @@ class RokuRemote(RokuEntity, RemoteEntity): 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 524ef2c5420..96d58927959 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 39b2f6a8378..04df44d6fa8 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 9f89b0faa50..e3823228887 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 583fd0a70c6..bd97d9e72d4 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 04df44d6fa8..3b2aa3bccf9 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 e3823228887..60971abf7cc 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 5009e25cb60..a71a93b970e 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 3b2aa3bccf9..68cfbf749ae 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 60971abf7cc..8e7a45105d4 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 ec0162af915..1a680a7fb65 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 c862983119a..f9ae7f62e18 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 @@ class GwSwitch(PlugwiseEntity, SwitchEntity): self._members = members self._model = model - self._is_on = False - self._icon = SWITCH_ICON + self._attr_is_on = False - @property - def is_on(self): - """Return true if device is on.""" - return self._is_on - - @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 @@ class GwSwitch(PlugwiseEntity, SwitchEntity): 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 | 111 +----------------- .../components/asuswrt/config_flow.py | 4 - tests/components/asuswrt/test_config_flow.py | 67 +---------- 3 files changed, 4 insertions(+), 178 deletions(-) diff --git a/homeassistant/components/asuswrt/__init__.py b/homeassistant/components/asuswrt/__init__.py index 1a680a7fb65..f735e520bdc 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 5a20880b4b0..a06071a33d1 100644 --- a/homeassistant/components/asuswrt/config_flow.py +++ b/homeassistant/components/asuswrt/config_flow.py @@ -165,10 +165,6 @@ class AsusWrtFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 a6e24b09462..7fe2681bafd 100644 --- a/tests/components/asuswrt/test_config_flow.py +++ b/tests/components/asuswrt/test_config_flow.py @@ -14,7 +14,7 @@ from homeassistant.components.asuswrt.const import ( 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 0842fd967b0..773c0ff0e6e 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 68cfbf749ae..f54627b7617 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 8e7a45105d4..1c8f174668f 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 ca65a66b4da..dfaea46c615 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 .const import ( ) 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 @@ class SmileBinarySensor(PlugwiseEntity, BinarySensorEntity): 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 @@ class PwNotifySensor(SmileBinarySensor): 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 5d802fb42a0..69080b364f0 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 9e4557276ca..924fa284e93 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 f54627b7617..45d1f9cc0d2 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 1c8f174668f..600d7ddf5cf 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 36a7bdf1ef1..88bd545c0a9 100644 --- a/homeassistant/components/tuya/light.py +++ b/homeassistant/components/tuya/light.py @@ -170,6 +170,17 @@ LIGHTS: dict[str, tuple[TuyaLightEntityDescription, ...]] = { 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 dfaea46c615..e83904ab25d 100644 --- a/homeassistant/components/plugwise/binary_sensor.py +++ b/homeassistant/components/plugwise/binary_sensor.py @@ -9,7 +9,6 @@ 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 ( COORDINATOR, @@ -22,6 +21,7 @@ from .const import ( 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 1063254299e..ff1325ffbf9 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -19,7 +19,6 @@ from homeassistant.config_entries import ConfigEntry 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 @@ from .const import ( 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 00000000000..ed6dad01f85 --- /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 8a2526d4811..f33ba98235b 100644 --- a/homeassistant/components/plugwise/entity.py +++ b/homeassistant/components/plugwise/entity.py @@ -11,21 +11,23 @@ from homeassistant.const import ( ) 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 e851941cf27..d1d35e3134b 100644 --- a/homeassistant/components/plugwise/gateway.py +++ b/homeassistant/components/plugwise/gateway.py @@ -3,12 +3,7 @@ from __future__ import annotations 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.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.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import ( COORDINATOR, DEFAULT_PORT, - DEFAULT_SCAN_INTERVAL, - DEFAULT_TIMEOUT, DEFAULT_USERNAME, DOMAIN, GATEWAY, @@ -32,6 +24,7 @@ from .const import ( 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 9d3f2d780b1..3cba9f59651 100644 --- a/homeassistant/components/plugwise/sensor.py +++ b/homeassistant/components/plugwise/sensor.py @@ -20,7 +20,6 @@ from homeassistant.const import ( ) 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 @@ from .const import ( 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 f9ae7f62e18..e5436389fca 100644 --- a/homeassistant/components/plugwise/switch.py +++ b/homeassistant/components/plugwise/switch.py @@ -10,9 +10,9 @@ 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 .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 5986cb18c75..87bfc7f89bc 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 45d1f9cc0d2..ef3626b011a 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 600d7ddf5cf..73272640802 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 0137e26a8a2..1264fa2c1df 100644 --- a/pylint/plugins/hass_enforce_type_hints.py +++ b/pylint/plugins/hass_enforce_type_hints.py @@ -10,7 +10,8 @@ from pylint.interfaces import IAstroidChecker 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 b2ea33fe2dd..43ff7548dd6 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 d490120faf8..41453117c13 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -558,7 +558,7 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity): 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 7404ceb4c7f..f9535292e69 100644 --- a/homeassistant/components/influxdb/sensor.py +++ b/homeassistant/components/influxdb/sensor.py @@ -14,6 +14,7 @@ from homeassistant.components.sensor import ( 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 @@ class InfluxSensor(SensorEntity): 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 86a32877a79..6bc2014d24e 100644 --- a/tests/components/influxdb/test_sensor.py +++ b/tests/components/influxdb/test_sensor.py @@ -44,13 +44,22 @@ BASE_V1_QUERY = { "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 8babaab4e25..be94c4a9a7f 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 00000000000..e8c0a0711dc --- /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 | 519 ++++++++++---------- 2 files changed, 261 insertions(+), 265 deletions(-) diff --git a/homeassistant/components/plugwise/const.py b/homeassistant/components/plugwise/const.py index a885a047f7a..4c221b2860a 100644 --- a/homeassistant/components/plugwise/const.py +++ b/homeassistant/components/plugwise/const.py @@ -9,9 +9,7 @@ DOMAIN = "plugwise" 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 @@ ZEROCONF_MAP = { "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 3cba9f59651..d55c2df4df9 100644 --- a/homeassistant/components/plugwise/sensor.py +++ b/homeassistant/components/plugwise/sensor.py @@ -6,6 +6,7 @@ from plugwise.smile import Smile from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, + SensorEntityDescription, SensorStateClass, ) from homeassistant.config_entries import ConfigEntry @@ -24,207 +25,259 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback 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, -] +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, + ), +) -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", -} +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 @@ class SmileSensor(PlugwiseEntity, SensorEntity): 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 @@ class SmileSensor(PlugwiseEntity, SensorEntity): 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 @@ class PwThermostatSensor(SmileSensor): 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 @@ class PwPowerSensor(SmileSensor): 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 @@ class PwPowerSensor(SmileSensor): 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 988beaa8fb6..8bc1fa42874 100644 --- a/homeassistant/components/rfxtrx/switch.py +++ b/homeassistant/components/rfxtrx/switch.py @@ -7,7 +7,7 @@ import RFXtrx as rfxtrxmod 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 @@ from . import ( 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 @@ async def async_setup_entry( 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 @@ async def async_setup_entry( 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 @@ class RfxtrxSwitch(RfxtrxCommandEntity, SwitchEntity): 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 @@ class RfxtrxSwitch(RfxtrxCommandEntity, SwitchEntity): 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 96368d166d7..175b455da6b 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 6c22ee02920..a4560934ee1 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 5e7c2ef1938..686fffec252 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 7492bd536e3..e161f818c8c 100644 --- a/homeassistant/components/songpal/media_player.py +++ b/homeassistant/components/songpal/media_player.py @@ -212,13 +212,18 @@ class SongpalEntity(MediaPlayerEntity): @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 f3004ef22e2..a9ca62ecb09 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 @@ HOST = "0.0.0.0" ENDPOINT = f"http://{HOST}:10000/sony" MODEL = "model" MAC = "mac" +WIRELESS_MAC = "wmac" SW_VERSION = "sw_ver" CONF_DATA = { @@ -20,7 +22,7 @@ CONF_DATA = { } -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 0fd5644e794..815995814f9 100644 --- a/tests/components/songpal/test_media_player.py +++ b/tests/components/songpal/test_media_player.py @@ -29,6 +29,7 @@ from . import ( 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 test_services(hass): 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 test_services(hass): ) 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 b89de457751..6eec61b49e2 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 84b76d98658..0c906b3e268 100644 --- a/homeassistant/components/yeelight/light.py +++ b/homeassistant/components/yeelight/light.py @@ -7,7 +7,8 @@ import math 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 @@ class YeelightGenericLight(YeelightEntity, LightEntity): 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 @@ class YeelightGenericLight(YeelightEntity, LightEntity): 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 7820e17a978..351cb879da9 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 ef3626b011a..d33b08c78ce 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 73272640802..e62662b7d79 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 b48cfc5402a..81972ca4352 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 059a47b53a1..2e37daaf9dd 100644 --- a/tests/components/yeelight/test_light.py +++ b/tests/components/yeelight/test_light.py @@ -219,8 +219,8 @@ async def test_services(hass: HomeAssistant, caplog): 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 test_services(hass: HomeAssistant, caplog): 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 test_services(hass: HomeAssistant, caplog): 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 test_services(hass: HomeAssistant, caplog): 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 test_services(hass: HomeAssistant, caplog): 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 test_services(hass: HomeAssistant, caplog): ) # 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 test_services(hass: HomeAssistant, caplog): {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 test_services(hass: HomeAssistant, caplog): 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 4c5c9d72497..9a3ae9f8d6c 100644 --- a/homeassistant/components/ihc/__init__.py +++ b/homeassistant/components/ihc/__init__.py @@ -4,115 +4,20 @@ import logging 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 00000000000..a68230c9900 --- /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 00000000000..2a54c1ef543 --- /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 00000000000..e3a85b85972 --- /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 273c9cc6a42..54a0735186f 100644 --- a/homeassistant/components/synology_dsm/common.py +++ b/homeassistant/components/synology_dsm/common.py @@ -16,6 +16,7 @@ from synology_dsm.api.storage.storage import SynoStorage from synology_dsm.api.surveillance_station import SynoSurveillanceStation from synology_dsm.exceptions import ( SynologyDSMAPIErrorException, + SynologyDSMException, SynologyDSMLoginFailedException, SynologyDSMRequestException, ) @@ -237,7 +238,11 @@ class SynoApi: 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 91ad49c5f84..256ad5eef8e 100644 --- a/homeassistant/components/synology_dsm/config_flow.py +++ b/homeassistant/components/synology_dsm/config_flow.py @@ -267,7 +267,7 @@ class SynologyDSMFlowHandler(ConfigFlow, domain=DOMAIN): 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 | 42 ++++++++++++----- homeassistant/components/nanoleaf/button.py | 9 ++-- homeassistant/components/nanoleaf/entity.py | 11 +++-- homeassistant/components/nanoleaf/light.py | 47 +++---------------- 4 files changed, 49 insertions(+), 60 deletions(-) diff --git a/homeassistant/components/nanoleaf/__init__.py b/homeassistant/components/nanoleaf/__init__.py index 4560f340416..677c0d4cc2a 100644 --- a/homeassistant/components/nanoleaf/__init__.py +++ b/homeassistant/components/nanoleaf/__init__.py @@ -3,15 +3,17 @@ from __future__ import annotations 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 2d9388196c1..5297e98c88e 100644 --- a/homeassistant/components/nanoleaf/button.py +++ b/homeassistant/components/nanoleaf/button.py @@ -7,6 +7,7 @@ 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 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 c6efed91787..8df9e243d71 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 b4eb89ac8c7..29c7cb786e6 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.components.light import ( ) 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 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( _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 @@ class NanoleafLight(NanoleafEntity, LightEntity): """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 | 152 +++--- tests/components/homekit/test_type_lights.py | 462 ++++++++++++++++-- 2 files changed, 522 insertions(+), 92 deletions(-) diff --git a/homeassistant/components/homekit/type_lights.py b/homeassistant/components/homekit/type_lights.py index cdff3105ec3..081f6f1bdd4 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 @@ from homeassistant.components.light import ( 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.const import ( 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 @@ from .const import ( _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 @@ class Light(HomeAccessory): 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 @@ class Light(HomeAccessory): 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 @@ class Light(HomeAccessory): # 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 @@ class Light(HomeAccessory): ) 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]}") + 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 - # 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) - ): + 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 @@ class Light(HomeAccessory): ): 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 @@ class Light(HomeAccessory): 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)) + 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 color temperature - if self.color_temp_supported: - color_temp = attributes.get(ATTR_COLOR_TEMP) + # 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 8e7b60b0a47..6835629be37 100644 --- a/tests/components/homekit/test_type_lights.py +++ b/tests/components/homekit/test_type_lights.py @@ -5,7 +5,11 @@ from datetime import timedelta 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 @@ from homeassistant.components.light import ( 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,19 +976,17 @@ 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 == 50 - + 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: 2700, + HAP_REPR_VALUE: 500, }, ] }, @@ -803,11 +995,221 @@ 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_COLOR_TEMP] == 500 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 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 == 100 + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_color_temp_iid, + HAP_REPR_VALUE: 200, + }, + ] + }, + "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_RGBWW_COLOR] == (0, 0, 0, 220, 35) + assert len(events) == 2 + 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 00726f6db38..4ab9342de62 100644 --- a/homeassistant/components/accuweather/weather.py +++ b/homeassistant/components/accuweather/weather.py @@ -17,7 +17,12 @@ from homeassistant.components.weather import ( 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 @@ class AccuWeatherEntity(CoordinatorEntity, WeatherEntity): """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 0b6cfff6bae..859aa834581 100644 --- a/homeassistant/components/nest/diagnostics.py +++ b/homeassistant/components/nest/diagnostics.py @@ -12,7 +12,7 @@ from google_nest_sdm.exceptions import ApiException 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 ca761e8987f..e80ca84d58f 100644 --- a/tests/components/nest/common.py +++ b/tests/components/nest/common.py @@ -102,6 +102,24 @@ TEST_CONFIG_HYBRID = 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 d21920b9e6f..843c9b582ae 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 import config_entries, data_entry_flow 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 b603019da81..cf6c9c5b20f 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 3a78877a235..cbf1bfe2d48 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 6e729a8f1b5..3846f4945a9 100644 --- a/homeassistant/components/amcrest/camera.py +++ b/homeassistant/components/amcrest/camera.py @@ -515,8 +515,8 @@ class AmcrestCam(Camera): 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 81d4a3f0645..9b968385602 100644 --- a/homeassistant/components/androidtv/__init__.py +++ b/homeassistant/components/androidtv/__init__.py @@ -18,6 +18,7 @@ from homeassistant.const import ( 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 @@ from .const import ( 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 0ec37fdeb6f..8f0efc34799 100644 --- a/homeassistant/components/androidtv/config_flow.py +++ b/homeassistant/components/androidtv/config_flow.py @@ -11,9 +11,8 @@ from homeassistant import config_entries 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 @@ class AndroidTVFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 03b1e679961..1ab592143c6 100644 --- a/homeassistant/components/androidtv/media_player.py +++ b/homeassistant/components/androidtv/media_player.py @@ -51,12 +51,13 @@ from homeassistant.const import ( ) 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 @@ from .const import ( DEVICE_ANDROIDTV, DEVICE_CLASSES, DOMAIN, - PROP_ETHMAC, - PROP_WIFIMAC, SIGNAL_CONFIG_ENTITY, ) @@ -343,7 +342,7 @@ class ADBDevice(MediaPlayerEntity): 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 4411945c71b..7cc14bbd7b5 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 @@ def patch_connect(success): } -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 @@ PATCH_ANDROIDTV_UPDATE_EXCEPTION = patch( "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 757be8f6d8d..991d3757749 100644 --- a/tests/components/androidtv/test_config_flow.py +++ b/tests/components/androidtv/test_config_flow.py @@ -31,6 +31,7 @@ from homeassistant.components.androidtv.const import ( 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 @@ from tests.components.androidtv.patchers import isfile 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 @@ PATCH_SETUP_ENTRY = patch( 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 e97de0fc928..a0bab1736ff 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 667d94695d3..8786a09b215 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -586,11 +586,11 @@ class AvmWrapper(FritzBoxTools): ) 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 @@ class AvmWrapper(FritzBoxTools): 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 6155cdc5914..5e4b18eebca 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 b0cee4fbac0..e8017e7d849 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 f04a7459543..4380b1397b6 100644 --- a/homeassistant/components/tplink/entity.py +++ b/homeassistant/components/tplink/entity.py @@ -54,6 +54,7 @@ class CoordinatedTPLinkEntity(CoordinatorEntity): 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 50249f54f03..02b7404201a 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 8786a09b215..79acc54b9b2 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -593,13 +593,6 @@ class AvmWrapper(FritzBoxTools): 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 @@ class AvmWrapper(FritzBoxTools): "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 55e83dc52bf..37100f1a759 100644 --- a/homeassistant/components/synology_dsm/const.py +++ b/homeassistant/components/synology_dsm/const.py @@ -382,6 +382,5 @@ SURVEILLANCE_SWITCH: tuple[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 00000000000..5771bee5bd3 --- /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 02b7404201a..beeaa21bf27 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 @@ def _patch_single_discovery(device=None, no_device=False): 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 e579be61df2..00000000000 --- 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 00000000000..4e3d4f01f20 --- /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 00000000000..13dd14bbdda --- /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 00000000000..d09ea72b70a --- /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 5e87fb71e2b..205468b3e4f 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 6044cdb277c..7cc27c2e1e7 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 30ca953b243..2ed18baa178 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 48de8c99684..6d5edbd8e1f 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 d1ee1310cad..b742fb36ec5 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 ad100c4b83e..dcbaf4bcb2e 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 4789dcc042b..66ffd692374 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 a4978fbb6bc..39b481f85b9 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 15a76c31f48..f323efbabff 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 f49061c90aa..d63c92365cb 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 8d52bf30cca..eba76886439 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 629e98f6986..dde91a52d9a 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 89ea2c39090..5712544feb3 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 832b86e2a7a..be98268cf54 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 71ce79519ed..ae835e6db38 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 7e59f93c77e..a5588a7f0d5 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 1fd1c2b4d98..d71fbe849e0 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 829a2b0a048..d1abe4aa1be 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 9de0612b2f7..c31366f17d3 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 00000000000..4ede47e2a32 --- /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 e417002cb90..2614230087b 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 00000000000..6ec2488368b --- /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 df4c08995f3..fed43b2d96f 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 ac7a9e38565..e76921b945b 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 homeassistant.helpers import config_validation as cv 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 @@ PLATFORMS = [ ] _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 @@ def roku_exception_handler(func: Callable) -> Callable: 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 f921dabdf4a..a7c52424d97 100644 --- a/homeassistant/components/lifx/light.py +++ b/homeassistant/components/lifx/light.py @@ -51,6 +51,7 @@ import homeassistant.helpers.config_validation as cv 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 @@ class LIFXManager: 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 @@ class LIFXManager: 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 191e8fa97e9..37e22052167 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 205468b3e4f..f4563b18bbf 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 e9badfc0479..1e637b02b1a 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 a3331b5c991..238c25ad917 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 d33b08c78ce..67dad92c5d0 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 e62662b7d79..029138806b5 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 5b0d7634996..f084302841e 100644 --- a/homeassistant/components/roku/coordinator.py +++ b/homeassistant/components/roku/coordinator.py @@ -9,11 +9,14 @@ from rokuecp.models import Device 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 @@ class RokuDataUpdateCoordinator(DataUpdateCoordinator[Device]): _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 91e1eeb3257..01046ded7c2 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 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = ( ), ) +_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 @@ def sonarr_exception_handler(func): _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 @@ class SonarrSensor(SonarrEntity, SensorEntity): 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 8acf7d5b2c8..cc15376efb1 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 e4c7ac4e658..8bbf6197a10 100644 --- a/homeassistant/components/dsmr/config_flow.py +++ b/homeassistant/components/dsmr/config_flow.py @@ -147,36 +147,6 @@ class DSMRFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """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 692870d7037..669fcfac386 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 21200789fbc..136fde657e5 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 67dad92c5d0..e03e8eafc36 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 029138806b5..fd769f4cf15 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 1a220eef7a2..374a8957bcc 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 88bd545c0a9..0fb75dbe6a8 100644 --- a/homeassistant/components/tuya/light.py +++ b/homeassistant/components/tuya/light.py @@ -181,6 +181,17 @@ LIGHTS: dict[str, tuple[TuyaLightEntityDescription, ...]] = { 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 fc3d8d86baf..6be35e0102b 100644 --- a/homeassistant/components/tuya/switch.py +++ b/homeassistant/components/tuya/switch.py @@ -290,6 +290,15 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { 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 11b2f663f19..f43f9a4d8ce 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 0fb75dbe6a8..4c35c850c33 100644 --- a/homeassistant/components/tuya/light.py +++ b/homeassistant/components/tuya/light.py @@ -108,6 +108,13 @@ LIGHTS: dict[str, tuple[TuyaLightEntityDescription, ...]] = { 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 be94c4a9a7f..8bc20400dba 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 02b78e164ac..d366ae39961 100644 --- a/homeassistant/components/netgear/const.py +++ b/homeassistant/components/netgear/const.py @@ -5,14 +5,14 @@ from homeassistant.const import Platform 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 b358939d122..722bcb27ae0 100644 --- a/homeassistant/components/netgear/router.py +++ b/homeassistant/components/netgear/router.py @@ -147,6 +147,7 @@ class NetgearRouter: "ip": None, "ssid": None, "conn_ap_mac": None, + "allow_or_block": None, } return True @@ -200,6 +201,13 @@ class NetgearRouter: 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 00000000000..cf8cd835f89 --- /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 c2ebdb5cb0f..ec8ca77bded 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 0a2460edca1..feef06746bd 100644 --- a/tests/components/renault/test_binary_sensor.py +++ b/tests/components/renault/test_binary_sensor.py @@ -4,7 +4,7 @@ from unittest.mock import patch 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 e5ba3a05941..604e4daaf35 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 e03e8eafc36..8dc8a0f5bfd 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 fd769f4cf15..8bb1d6564e5 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 37e22052167..1ede9c62e16 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 f4563b18bbf..321cb7f622b 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 3fecfd8ba48..d71571d2594 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 a4d5ce3045d..1d6ce675e74 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 b908bf01a32..39ce36074ad 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 5cded6a179d..18ed69348b0 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 c8ee1d91368..b907cc50cbb 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 b641ec7e8c0..5040a322b62 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 f827c3a373f..210779eec45 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 061e0bbc0cb..75d7905ff0e 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 af2f9961956..b47a6c94ac8 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 30f03663de1..f37fa1c3da2 100644 --- a/homeassistant/components/version/config_flow.py +++ b/homeassistant/components/version/config_flow.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import Any -from pyhaversion.consts import HaVersionChannel, HaVersionSource import voluptuous as vol from homeassistant import config_entries @@ -75,8 +74,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ): 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @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 8575b17a703..8f1005961e8 100644 --- a/homeassistant/components/version/const.py +++ b/homeassistant/components/version/const.py @@ -41,12 +41,12 @@ VERSION_SOURCE_VERSIONS: Final = "Home Assistant Versions" 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 @@ BOARD_MAP: Final[dict[str, str]] = { 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 00000000000..2ba31bc8870 --- /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 00000000000..1c9c8df4c62 --- /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 8bc20400dba..db20f089e4b 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 6eec61b49e2..7e56031c2bf 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 00000000000..42b510f9417 --- /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 00000000000..40d850b8483 --- /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 00000000000..40105d100d4 --- /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 00000000000..140c397ecde --- /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 00000000000..a4b257bdab5 --- /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 00000000000..ccbf6989f07 --- /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 00000000000..0d53512d1cb --- /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 00000000000..df2a14d3bbd --- /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 00000000000..17e64ff1602 --- /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 00000000000..1a7177ca470 --- /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 00000000000..b955ade21db --- /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 00000000000..84c4f406495 --- /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 00000000000..4986de744c0 --- /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 00000000000..c759809df15 --- /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 00000000000..81d09d5c350 --- /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 c521bd85e3e..061dcd3a0ad 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -378,5 +378,6 @@ FLOWS = [ "zerproc", "zha", "zwave", - "zwave_js" + "zwave_js", + "zwave_me" ] diff --git a/homeassistant/generated/zeroconf.py b/homeassistant/generated/zeroconf.py index da48577a146..06d1940f23d 100644 --- a/homeassistant/generated/zeroconf.py +++ b/homeassistant/generated/zeroconf.py @@ -144,6 +144,10 @@ ZEROCONF = { "_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 8dc8a0f5bfd..3a5da629839 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 8bb1d6564e5..c86988eb62a 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 00000000000..da2457db55e --- /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 00000000000..9c7c9f51e24 --- /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 ddf324278cf..ce5d92c5f62 100644 --- a/homeassistant/components/wiz/__init__.py +++ b/homeassistant/components/wiz/__init__.py @@ -81,6 +81,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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 773c0ff0e6e..90a6347dbc5 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 3a5da629839..5e2c0ac5bda 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 c86988eb62a..bf0123fea98 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 5aac52f6f7b..8fa18ce3142 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -34,6 +34,7 @@ from homeassistant.components.media_player.const import ( 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.exceptions import HomeAssistantError 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 @@ def cmd( return cmd_wrapper -class LgWebOSMediaPlayerEntity(MediaPlayerEntity): +class LgWebOSMediaPlayerEntity(RestoreEntity, MediaPlayerEntity): """Representation of a LG webOS Smart TV.""" def __init__( @@ -144,8 +146,12 @@ class LgWebOSMediaPlayerEntity(MediaPlayerEntity): 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 @@ class LgWebOSMediaPlayerEntity(MediaPlayerEntity): 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 @@ class LgWebOSMediaPlayerEntity(MediaPlayerEntity): @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 @@ class LgWebOSMediaPlayerEntity(MediaPlayerEntity): 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 c249b491d9a..450d3d90377 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 1a0a94198cc..2f8dcc18921 100644 --- a/homeassistant/components/overkiz/config_flow.py +++ b/homeassistant/components/overkiz/config_flow.py @@ -128,11 +128,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 2bef16ec2dd..87487d53c66 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 42380904583..c9551aa555c 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 a88c445614a..e1a98482635 100644 --- a/homeassistant/components/wiz/const.py +++ b/homeassistant/components/wiz/const.py @@ -9,8 +9,6 @@ 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/light.py b/homeassistant/components/wiz/light.py index bc5ac078ec7..2c4485c5b72 100644 --- a/homeassistant/components/wiz/light.py +++ b/homeassistant/components/wiz/light.py @@ -28,7 +28,7 @@ from homeassistant.util.color import ( 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 e6e34c73c3b..ffe75910b40 100644 --- a/homeassistant/components/wiz/switch.py +++ b/homeassistant/components/wiz/switch.py @@ -4,13 +4,14 @@ from __future__ import annotations 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 ceaab797d5e..42be5758130 100644 --- a/homeassistant/components/wiz/utils.py +++ b/homeassistant/components/wiz/utils.py @@ -3,7 +3,7 @@ from __future__ import annotations 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 0bf10ca71c6..6c305242f5f 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 25590c712d9..c96465a671f 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 | 78 ++++++++++----------- tests/components/filesize/test_sensor.py | 55 +++++++++++++-- 2 files changed, 86 insertions(+), 47 deletions(-) diff --git a/homeassistant/components/filesize/sensor.py b/homeassistant/components/filesize/sensor.py index e409b346c6d..56542a0aadd 100644 --- a/homeassistant/components/filesize/sensor.py +++ b/homeassistant/components/filesize/sensor.py @@ -4,10 +4,14 @@ from __future__ import annotations 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 @@ _LOGGER = logging.getLogger(__name__) 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() + 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 - @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 { + 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 72d0d112f17..fa85ce41437 100644 --- a/tests/components/filesize/test_sensor.py +++ b/tests/components/filesize/test_sensor.py @@ -7,7 +7,9 @@ import pytest 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_DIR = os.path.join(os.path.dirname(__file__)) 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 ce5d92c5f62..15b46a14ae0 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 de0b0e3f947..1ddaced401f 100644 --- a/homeassistant/components/wiz/entity.py +++ b/homeassistant/components/wiz/entity.py @@ -23,11 +23,16 @@ class WizToggleEntity(CoordinatorEntity, ToggleEntity): 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 90a6347dbc5..82e1393dec3 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 5e2c0ac5bda..c0cb46ef0ec 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 bf0123fea98..04e53bd6f57 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 7fcc2268d9b..e856d746b3d 100644 --- a/homeassistant/components/samsungtv/media_player.py +++ b/homeassistant/components/samsungtv/media_player.py @@ -245,7 +245,7 @@ class SamsungTVDevice(MediaPlayerEntity): 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 f64634acd3b..dca7e4366f3 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 975fa3f5836..d418373e599 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -50,6 +50,7 @@ from homeassistant.const import ( CAST_APP_ID_HOMEASSISTANT_LOVELACE, EVENT_HOMEASSISTANT_STOP, STATE_IDLE, + STATE_OFF, STATE_PAUSED, STATE_PLAYING, ) @@ -636,7 +637,7 @@ class CastDevice(MediaPlayerEntity): 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 bd22a558314..51fe4a086a6 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 27906e0401f..b8d159893fe 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -89,7 +89,7 @@ from .util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM, UnitSystem # 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 @@ class Config: # 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 0b2804f821d..0c52012e43c 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 05c72f10db5..7e9086f4467 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 5d51017bfd6..9b340096fde 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 bd97d9e72d4..a41fead628c 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 c0cb46ef0ec..eacf7a91006 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 04e53bd6f57..7e78e94010c 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 b8f15a811db..48dca4d42ed 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 b84ddaad404..150ce3a7eb6 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -134,6 +134,10 @@ class MqttBinarySensor(MqttEntity, BinarySensorEntity, RestoreEntity): 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 @@ class MqttBinarySensor(MqttEntity, BinarySensorEntity, RestoreEntity): # 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 a8cad4b09f8..137627047bc 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -180,6 +180,10 @@ class MqttSensor(MqttEntity, SensorEntity, RestoreEntity): 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 @@ class MqttSensor(MqttEntity, SensorEntity, RestoreEntity): # 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 e40fe901b09..ca1e5a0a91c 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -399,13 +399,20 @@ class SonosSpeaker: 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 @@ class SonosSpeaker: 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 0b58b36014a..2ac0e09634c 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 c78531f645f..d6841e0f81b 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 00000000000..3a870e47986 --- /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 8fd08509174..1bafcdf8e44 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 6292d3c18dc..7f3eb3a0796 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 035492a01fe..e6e5f9a008a 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 2c604126b3c..1e1bccd1fb7 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 b1a57f0ead2..232e757b643 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 c1c00bc1df6..c19d9c39ca9 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 545ddf77c8d..2f2c335ebd5 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 a0d7fb1cbfe..4a8760b91c5 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 dea99c02991..f51b432b642 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 95f8d600901..eeba049a9f2 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 c3eea93473a..ea40df76ae7 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 effe871645a..27e26acabde 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 c6da7570d5e..4755e9f5d09 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 617237c8768..9ec7eac14c0 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 81d140f3d4f..f481be62e73 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 92d0c3f84d3..a1163f5cf02 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 00000000000..47c1d650fa5 --- /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 00000000000..ba91812d06f --- /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 00000000000..b5512c539fe --- /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 00000000000..c07b4b2b5f8 --- /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 00000000000..4a7be43ddb7 --- /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 | 281 +++++-- 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 | 779 ++++++++++++++++-- 13 files changed, 1225 insertions(+), 187 deletions(-) create mode 100644 homeassistant/components/elkm1/discovery.py diff --git a/.coveragerc b/.coveragerc index db20f089e4b..05f1ed64c33 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 6f586423552..8ab9c0ac73f 100644 --- a/homeassistant/components/elkm1/__init__.py +++ b/homeassistant/components/elkm1/__init__.py @@ -6,6 +6,7 @@ import logging 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.core import HomeAssistant, ServiceCall, callback 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 @@ from .const import ( 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 @@ DEVICE_SCHEMA_SUBDOMAIN = vol.Schema( } ) -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 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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 @@ class ElkEntity(Entity): 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 @@ class ElkEntity(Entity): @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 905aa35ad19..19a3cf88473 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 @@ PROTOCOL_MAP = { "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, +} -async def validate_input(data): +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: 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.""" - errors = {} 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") + 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() - try: - info = await validate_input(user_input) + 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)}), + ) - 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" + 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") - if "base" not in errors: - await self.async_set_unique_id(user_input[CONF_PREFIX]) - self._abort_if_unique_id_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 self.importing: - return self.async_create_entry(title=info["title"], data=user_input) + if importing: + return None, 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], - }, - ) + 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: + 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="user", data_schema=DATA_SCHEMA, errors=errors + step_id="discovered_connection", + data_schema=vol.Schema(base_schmea), + errors=errors, + description_placeholders=_placeholders_from_device(device), + ) + + 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="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 4d2dac4b1de..80d594fce0a 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 @@ from homeassistant.const import ATTR_CODE, CONF_ZONE DOMAIN = "elkm1" +LOGIN_TIMEOUT = 15 + CONF_AUTO_CONFIGURE = "auto_configure" CONF_AREA = "area" CONF_COUNTER = "counter" @@ -18,9 +22,8 @@ CONF_SETTING = "setting" 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 00000000000..10d9f7b6e40 --- /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 2d84604d53a..a72a0221907 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 bf0da956d44..35672d5df80 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 04fd3c189b5..238f1c3d30e 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 2e9672f99eb..bf42a11f951 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -67,6 +67,10 @@ DHCP = [ "domain": "broadlink", "macaddress": "B4430D*" }, + { + "domain": "elkm1", + "macaddress": "00409D*" + }, { "domain": "emonitor", "hostname": "emonitor*", diff --git a/requirements_all.txt b/requirements_all.txt index eacf7a91006..f6b385f5bd1 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 7e78e94010c..aaba6417465 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 8ae7f6d7b49..128d0a0d777 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 ab5ebba79eb..76db04944b5 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, +) + +from tests.common import MockConfigEntry + +DHCP_DISCOVERY = dhcp.DhcpServiceInfo(MOCK_IP_ADDRESS, "", MOCK_MAC) +ELK_DISCOVERY_INFO = asdict(ELK_DISCOVERY) +MODULE = "homeassistant.components.elkm1" -def mock_elk(invalid_auth=None, sync_complete=None): - """Mock m1lib Elk.""" - - def handler_callbacks(type_, callback): - nonlocal invalid_auth, sync_complete - - 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): - """Test we can setup a secure elk.""" - - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} +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( - "homeassistant.components.elkm1.config_flow.elkm1.Elk", - return_value=mocked_elk, - ), patch( + 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.""" + + 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_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,19 @@ 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, - ), patch( + 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 +429,43 @@ 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"] == {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"], + { + "protocol": "secure", + "address": "1.2.3.4", + "username": "test-username", + "password": "test-password", + "prefix": "", + }, + ) + + assert result2["type"] == "form" + 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_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) + + with patch( + "homeassistant.components.elkm1.config_flow.elkm1.Elk", + return_value=mocked_elk, + ): + 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( - "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", @@ -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 c87d2097929..3d68ebbaee0 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 @@ SCAN_INTERVAL = 60 # Interval for updating the coordinator 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 693ebeead00..1654b780124 100644 --- a/homeassistant/components/tradfri/sensor.py +++ b/homeassistant/components/tradfri/sensor.py @@ -3,6 +3,7 @@ from __future__ import annotations 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 @@ from homeassistant.components.sensor import ( 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 @@ class TradfriSensor(TradfriBaseEntity, SensorEntity): 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 4db4ed4e585..63e6a6558c9 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 04f65344125..a36ff93a607 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 722bcb27ae0..3778c36d81a 100644 --- a/homeassistant/components/netgear/router.py +++ b/homeassistant/components/netgear/router.py @@ -123,8 +123,9 @@ class NetgearRouter: 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 56bc59e546f..2c61151bfaf 100644 --- a/homeassistant/components/tuya/binary_sensor.py +++ b/homeassistant/components/tuya/binary_sensor.py @@ -110,6 +110,14 @@ BINARY_SENSORS: dict[str, tuple[TuyaBinarySensorEntityDescription, ...]] = { ), 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 7fcea1095d4..3ee88d2d57b 100644 --- a/homeassistant/components/tuya/sensor.py +++ b/homeassistant/components/tuya/sensor.py @@ -295,6 +295,9 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = { ), *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 c2f0f96b6d5..1bd54e2214e 100644 --- a/homeassistant/components/octoprint/config_flow.py +++ b/homeassistant/components/octoprint/config_flow.py @@ -145,9 +145,15 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 2ce8a686c8b..e69d92e3cd0 100644 --- a/homeassistant/components/plugwise/config_flow.py +++ b/homeassistant/components/plugwise/config_flow.py @@ -97,12 +97,17 @@ class PlugwiseConfigFlow(ConfigFlow, domain=DOMAIN): _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 ee0d4eb43a3..6208091dd8d 100644 --- a/homeassistant/components/zwave_js/api.py +++ b/homeassistant/components/zwave_js/api.py @@ -133,6 +133,7 @@ APPLICATION_VERSION = "application_version" 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 @@ QR_PROVISIONING_INFORMATION_SCHEMA = vol.All( 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 40f60b9018a..f93ba4fbb93 100644 --- a/tests/components/zwave_js/test_api.py +++ b/tests/components/zwave_js/test_api.py @@ -29,6 +29,7 @@ from zwave_js_server.model.node import Node 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 71a5b89d534..b38ab3ef5d3 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 f6b385f5bd1..0e49d5e7a5a 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 aaba6417465..8a057ba0fb3 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 2bc65934477..f09102270cf 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 f100fb53dc8..55c31da6c89 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 e0cc9b715c1..b365c114b03 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 4f62d50da34..bd17aec92e6 100644 --- a/tests/helpers/test_event.py +++ b/tests/helpers/test_event.py @@ -46,8 +46,11 @@ from tests.common import async_fire_time_changed 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 93dd1b87b44..4e761bc3f47 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 63513c90360..d2c453f070d 100644 --- a/tests/util/test_dt.py +++ b/tests/util/test_dt.py @@ -9,8 +9,11 @@ DEFAULT_TIME_ZONE = dt_util.DEFAULT_TIME_ZONE 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 af7f43eae4d..461d94d0c67 100644 --- a/tests/util/test_json.py +++ b/tests/util/test_json.py @@ -26,14 +26,14 @@ TEST_BAD_SERIALIED = "THIS IS NOT JSON\n" 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 05f1ed64c33..61dccdb2d8b 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 7e56031c2bf..b78d12939a8 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 00000000000..4079b74a598 --- /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 00000000000..2e9ea834799 --- /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 00000000000..792d5f1b025 --- /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 00000000000..5907aa7300e --- /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 00000000000..4a18df0fc95 --- /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 00000000000..31e23565a6f --- /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 00000000000..2949dfb58ad --- /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 00000000000..a81ce1e1ce5 --- /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 061dcd3a0ad..2aab7c81481 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -95,6 +95,7 @@ FLOWS = [ "ezviz", "faa_delays", "fireservicerota", + "fivem", "fjaraskupan", "flick_electric", "flipr", diff --git a/requirements_all.txt b/requirements_all.txt index 0e49d5e7a5a..31994aa6489 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 8a057ba0fb3..df3f191abda 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 00000000000..019f9b82913 --- /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 00000000000..7af6e0fca48 --- /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 | 62 ++- 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, 837 insertions(+), 317 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 e83904ab25d..44d5e79ce16 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 @@ class SmileBinarySensor(PlugwiseEntity, BinarySensorEntity): 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 @@ class PwBinarySensor(SmileBinarySensor): 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 ff1325ffbf9..49c73049a9a 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -10,8 +10,9 @@ from homeassistant.components.climate.const import ( 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 .const import ( 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 @@ class PwThermostat(PlugwiseEntity, ClimateEntity): 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 @@ class PwThermostat(PlugwiseEntity, ClimateEntity): 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 @@ class PwThermostat(PlugwiseEntity, ClimateEntity): @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 @@ class PwThermostat(PlugwiseEntity, ClimateEntity): # 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 ed6dad01f85..b0b2c4b1294 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 @@ class PlugwiseDataUpdateCoordinator(DataUpdateCoordinator[bool]): ) 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 f33ba98235b..c57f9c9281f 100644 --- a/homeassistant/components/plugwise/entity.py +++ b/homeassistant/components/plugwise/entity.py @@ -14,14 +14,12 @@ from homeassistant.helpers.entity import DeviceInfo 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 @@ class PlugwiseEntity(CoordinatorEntity[bool]): @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 @@ class PlugwiseEntity(CoordinatorEntity[bool]): 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 d1d35e3134b..eaccab58454 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 5006b1e659d..7dc3bdd9b1d 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 d55c2df4df9..d2fd47d4b7d 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 @@ class PwAuxDeviceSensor(SmileSensor): 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 e5436389fca..4e5a00e3e6a 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" + continue - entities.append( - GwSwitch( - api, coordinator, device_properties["name"], dev_id, members, model - ) + 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 @@ class GwSwitch(PlugwiseEntity, SwitchEntity): 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 @@ class GwSwitch(PlugwiseEntity, SwitchEntity): 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 @@ class GwSwitch(PlugwiseEntity, SwitchEntity): @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 31994aa6489..a1e36db3cc5 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 f09102270cf..bd83e1505a2 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 df3f191abda..ef1086118bf 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 bd988b50e3a..c30a7382f27 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 65415285de2..fa49967437d 100644 --- a/tests/components/plugwise/conftest.py +++ b/tests/components/plugwise/conftest.py @@ -4,10 +4,10 @@ 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 -import jsonpickle from plugwise.exceptions import ( ConnectionFailedError, InvalidAuthentication, @@ -23,7 +23,7 @@ from tests.test_util.aiohttp import AiohttpClientMocker 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,17 +108,13 @@ 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( + 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 ) - 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 - ) - 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( + smile_mock.return_value.set_switch_state.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.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 00000000000..7fc843e4aae --- /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 5a3492a3c6b..00000000000 --- 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 238da9d846a..00000000000 --- 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 4fcb40c4cf8..00000000000 --- 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 feb6290c9c4..00000000000 --- 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 74d15fac374..00000000000 --- 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 3ea0a92387b..00000000000 --- 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 2d8ace6fa3f..00000000000 --- 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 7a9c3e9be01..00000000000 --- 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 d2f2f82bdf6..00000000000 --- 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 0aeca4cc18e..00000000000 --- 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 3f01f47fc5c..00000000000 --- 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 3a1c902932a..00000000000 --- 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 2b314f589b6..00000000000 --- 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 fbefc5bba25..00000000000 --- 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 3e061593953..00000000000 --- 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 88420a8a6bd..00000000000 --- 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 7e4532987b0..00000000000 --- 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 0d6e19967dc..00000000000 --- 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 ef325af7bc2..00000000000 --- 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 c229f64da04..8749be4c345 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 00000000000..017d1ce41e8 --- /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 ea46cd68054..00000000000 --- 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 750aa8b455c..00000000000 --- 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 604b9388969..00000000000 --- 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 048cc0f77dc..00000000000 --- 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 00000000000..43c47c2b5e0 --- /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 a78f45ead8a..00000000000 --- 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 eed9382a7e9..00000000000 --- 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 00000000000..3834eb516de --- /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 dab74fb74a2..00000000000 --- 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 b08f6d6093a..00000000000 --- 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 4a3e493b246..00000000000 --- 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 7325dff8271..00000000000 --- 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 bbb8ac98c1c..00000000000 --- 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 b0cab0e3f30..00000000000 --- 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 e58bc4c6d6f..00000000000 --- 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 b08f6d6093a..00000000000 --- 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 bbb8ac98c1c..00000000000 --- 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 bbb8ac98c1c..00000000000 --- 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 11ebae52f49..00000000000 --- 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 456fb6744d2..00000000000 --- 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 2fed3d18fd2..a7af9f9c0c9 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 c4f7e1c6b3d..d0d810804e2 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 3b5bff781e5..3498a7a8cf1 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 6355362fbd9..1e1fb4a0679 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 00000000000..2b79d22f6a3 --- /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 00000000000..fdc4ee87920 --- /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 59c589931f4..c70b0855b6d 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 a1e36db3cc5..a3efffdaa76 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 ef1086118bf..cf0136a8570 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 ba5470724cd..a88706a428f 100644 --- a/homeassistant/components/overkiz/const.py +++ b/homeassistant/components/overkiz/const.py @@ -61,6 +61,8 @@ OVERKIZ_DEVICE_TO_PLATFORM: dict[UIClass | UIWidget, Platform] = { 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 969a9508943..8b3222cf442 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 @@ SWITCH_DESCRIPTIONS: list[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 5b4a6e12459..6f14d5735fb 100644 --- a/homeassistant/components/tod/binary_sensor.py +++ b/homeassistant/components/tod/binary_sensor.py @@ -161,6 +161,21 @@ class TodSensor(BinarySensorEntity): 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 ef8088d6aab..06f29436d6e 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 b78d12939a8..973837d3ee5 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 0acb39801b5..c1f0401e2b0 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 77f76745ba4..f3b0b66df54 100644 --- a/homeassistant/components/influxdb/const.py +++ b/homeassistant/components/influxdb/const.py @@ -72,7 +72,7 @@ INFLUX_CONF_ORG = "org" 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 973837d3ee5..c8cc32b2d35 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 7dc3bdd9b1d..97783854614 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 1d0e18c79ea..96f73fda0f3 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 a88706a428f..e6c55af0749 100644 --- a/homeassistant/components/overkiz/const.py +++ b/homeassistant/components/overkiz/const.py @@ -5,8 +5,7 @@ from datetime import timedelta 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 @@ OVERKIZ_DEVICE_TO_PLATFORM: dict[UIClass | UIWidget, Platform] = { # 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 47c5c45f2ad..1c08d47622d 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 a3efffdaa76..a09b44aaf34 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 cf0136a8570..f6a835c8558 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 96f73fda0f3..3ced7b8d742 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 12ee32d532f..19468fcf686 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 a09b44aaf34..f045c11ad3a 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 f6a835c8558..13a39da4c4e 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 e6c55af0749..370e56feeeb 100644 --- a/homeassistant/components/overkiz/const.py +++ b/homeassistant/components/overkiz/const.py @@ -56,7 +56,7 @@ OVERKIZ_DEVICE_TO_PLATFORM: dict[UIClass | UIWidget, Platform] = { 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 8b3222cf442..8fd38816bcd 100644 --- a/homeassistant/components/overkiz/switch.py +++ b/homeassistant/components/overkiz/switch.py @@ -17,6 +17,7 @@ from homeassistant.components.switch import ( 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 @@ SWITCH_DESCRIPTIONS: list[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 | 65 +++++++------------ 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, 70 insertions(+), 118 deletions(-) diff --git a/homeassistant/components/plugwise/binary_sensor.py b/homeassistant/components/plugwise/binary_sensor.py index 44d5e79ce16..1f5cfacb37e 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 49c73049a9a..1cfb4ab7c67 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 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): 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 c57f9c9281f..2f73d38e5e3 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 + configuration_url: str | None = None + if entry := self.coordinator.config_entry: + configuration_url = f"http://{entry.data[CONF_HOST]}" - @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", + 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 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 - - 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 d2fd47d4b7d..47b4f898c25 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 @@ class PlugwiseSensorEnity(PlugwiseEntity, SensorEntity): 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 4e5a00e3e6a..6079862fe42 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 @@ class PlugwiseSwitchEntity(PlugwiseEntity, SwitchEntity): 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 @@ class PlugwiseSwitchEntity(PlugwiseEntity, SwitchEntity): 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 69080b364f0..b2356036eb9 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 3498a7a8cf1..47cbea1a8bc 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 5f3cc285517..7741ec36708 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 dfe9e8328a4..e7cf05a1ff0 100644 --- a/homeassistant/components/sonos/alarms.py +++ b/homeassistant/components/sonos/alarms.py @@ -8,11 +8,11 @@ from typing import TYPE_CHECKING, Any 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 @@ class SonosAlarms(SonosHouseholdCoordinator): 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 008e0b2cf57..534e5f5dd02 100644 --- a/homeassistant/components/sonos/binary_sensor.py +++ b/homeassistant/components/sonos/binary_sensor.py @@ -64,7 +64,7 @@ class SonosPowerEntity(SonosEntity, BinarySensorEntity): 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 @@ class SonosMicrophoneSensorEntity(SonosEntity, BinarySensorEntity): 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 abb0696360b..2d5dbc5195a 100644 --- a/homeassistant/components/sonos/const.py +++ b/homeassistant/components/sonos/const.py @@ -153,7 +153,7 @@ SONOS_CREATE_MIC_SENSOR = "sonos_create_mic_sensor" 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 53768431a1d..8565fe08e9c 100644 --- a/homeassistant/components/sonos/entity.py +++ b/homeassistant/components/sonos/entity.py @@ -7,7 +7,6 @@ import logging 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 homeassistant.helpers.entity import DeviceInfo, Entity 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 @@ class SonosEntity(Entity): 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 @@ class SonosEntity(Entity): """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 @@ class SonosEntity(Entity): 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 @@ class SonosEntity(Entity): 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 d7f1a2e6a96..bce1e3233c1 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 64c5b743809..fd651b7740c 100644 --- a/homeassistant/components/sonos/favorites.py +++ b/homeassistant/components/sonos/favorites.py @@ -14,6 +14,7 @@ from soco.exceptions import SoCoException 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 @@ class SonosFavorites(SonosHouseholdCoordinator): 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 625b54b941e..8e7f6fcf294 100644 --- a/homeassistant/components/sonos/helpers.py +++ b/homeassistant/components/sonos/helpers.py @@ -5,17 +5,18 @@ from collections.abc import Callable 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 @@ UID_POSTFIX = "01400" _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 soco_error( 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 soco_error( ) 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 f233b338279..0d76feae461 100644 --- a/homeassistant/components/sonos/household_coordinator.py +++ b/homeassistant/components/sonos/household_coordinator.py @@ -6,12 +6,12 @@ from collections.abc import Callable, Coroutine 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 @@ class SonosHouseholdCoordinator: _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 41453117c13..5f5220cd164 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -283,7 +283,7 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity): 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 2e6acd55a66..c9b8ec47583 100644 --- a/homeassistant/components/sonos/number.py +++ b/homeassistant/components/sonos/number.py @@ -12,7 +12,6 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback 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 @@ class SonosLevelEntity(SonosEntity, NumberEntity): 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 4e86edc2ca3..5cccc40ac5f 100644 --- a/homeassistant/components/sonos/sensor.py +++ b/homeassistant/components/sonos/sensor.py @@ -12,7 +12,8 @@ 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 .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 @@ class SonosBatteryEntity(SonosEntity, SensorEntity): 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 @@ class SonosBatteryEntity(SonosEntity, SensorEntity): 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 @@ class SonosAudioInputFormatSensorEntity(SonosEntity, SensorEntity): 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 ca1e5a0a91c..b9844303956 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -16,7 +16,7 @@ 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 -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 @@ from .const import ( 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 @@ class SonosSpeaker: 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 @@ class SonosSpeaker: 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 @@ class SonosSpeaker: # # 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 4e3303db45d..db10d9189e5 100644 --- a/homeassistant/components/sonos/switch.py +++ b/homeassistant/components/sonos/switch.py @@ -4,7 +4,7 @@ from __future__ import annotations 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 @@ from .const import ( 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_setup_entry( ) -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 @@ class SonosSwitchEntity(SonosEntity, SwitchEntity): 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 @@ class SonosAlarmEntity(SonosEntity, SwitchEntity): 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 @@ class SonosAlarmEntity(SonosEntity, SwitchEntity): 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 @@ class SonosAlarmEntity(SonosEntity, SwitchEntity): """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 3f000b14db7..960e343792d 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 484b209b6fa..225c45423a1 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 1aec7ebf39f..6315cc3e885 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 165dc2e5397..51f41d68320 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 1aec7ebf39f..6315cc3e885 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 165dc2e5397..51f41d68320 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 04a0aa6dc2c..9660ca71b9c 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 d7109aa65ee..885b7e3c65a 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 e1a5d20a987..93512d08238 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 a52c2948557..5b630973f67 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 351cb879da9..7aa46881968 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 18ed69348b0..fc0292523c2 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 f045c11ad3a..d725f46bb50 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 13a39da4c4e..7d59fc3c2b8 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 1f5cfacb37e..35d351b4094 100644 --- a/homeassistant/components/plugwise/binary_sensor.py +++ b/homeassistant/components/plugwise/binary_sensor.py @@ -99,11 +99,11 @@ class PlugwiseBinarySensorEntity(PlugwiseEntity, BinarySensorEntity): ).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 @@ class PlugwiseBinarySensorEntity(PlugwiseEntity, BinarySensorEntity): 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 @@ class PlugwiseNotifyBinarySensorEntity(PlugwiseBinarySensorEntity): 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 1cfb4ab7c67..89d7249ef38 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -150,8 +150,8 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): 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 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): "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 2f73d38e5e3..7fdad9f4a91 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 @@ class PlugwiseEntity(CoordinatorEntity[PlugwiseData]): 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 47b4f898c25..c99a5304ead 100644 --- a/homeassistant/components/plugwise/sensor.py +++ b/homeassistant/components/plugwise/sensor.py @@ -346,15 +346,15 @@ class PlugwiseSensorEnity(PlugwiseEntity, SensorEntity): ).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 @@ class PlugwiseAuxSensorEntity(PlugwiseSensorEnity): 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 6079862fe42..98288ca44b5 100644 --- a/homeassistant/components/plugwise/switch.py +++ b/homeassistant/components/plugwise/switch.py @@ -87,12 +87,12 @@ class PlugwiseSwitchEntity(PlugwiseEntity, SwitchEntity): 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 82e1393dec3..f1e44e5a774 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 bf42a11f951..a2193307b18 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -618,7 +618,7 @@ DHCP = [ }, { "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 35d351b4094..27ad691e55e 100644 --- a/homeassistant/components/plugwise/binary_sensor.py +++ b/homeassistant/components/plugwise/binary_sensor.py @@ -9,7 +9,6 @@ from homeassistant.helpers.entity import EntityCategory 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 89d7249ef38..5ecab66bbde 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.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ( - COORDINATOR, DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, DOMAIN, @@ -35,6 +33,7 @@ from .entity import PlugwiseEntity 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 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): 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 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): 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 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): 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 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): 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 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): 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 4c221b2860a..d8d0a040d5f 100644 --- a/homeassistant/components/plugwise/const.py +++ b/homeassistant/components/plugwise/const.py @@ -9,7 +9,6 @@ DOMAIN = "plugwise" 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 2b79d22f6a3..ef54efbe96d 100644 --- a/homeassistant/components/plugwise/diagnostics.py +++ b/homeassistant/components/plugwise/diagnostics.py @@ -6,7 +6,7 @@ from typing import Any 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 7fdad9f4a91..5fa28541815 100644 --- a/homeassistant/components/plugwise/entity.py +++ b/homeassistant/components/plugwise/entity.py @@ -12,6 +12,8 @@ from .coordinator import PlugwiseData, PlugwiseDataUpdateCoordinator 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 eaccab58454..6e518aad490 100644 --- a/homeassistant/components/plugwise/gateway.py +++ b/homeassistant/components/plugwise/gateway.py @@ -14,14 +14,11 @@ from homeassistant.helpers import device_registry as dr 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 c99a5304ead..fa516c88c6c 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.const import ( 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 @@ class PlugwiseSensorEnity(PlugwiseEntity, SensorEntity): """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 98288ca44b5..98ba1cd8183 100644 --- a/homeassistant/components/plugwise/switch.py +++ b/homeassistant/components/plugwise/switch.py @@ -3,7 +3,6 @@ from __future__ import annotations 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.config_entries import ConfigEntry 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 @@ class PlugwiseSwitchEntity(PlugwiseEntity, SwitchEntity): 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 @@ class PlugwiseSwitchEntity(PlugwiseEntity, SwitchEntity): 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 4079b74a598..2004aacd165 100644 --- a/homeassistant/components/fivem/__init__.py +++ b/homeassistant/components/fivem/__init__.py @@ -10,7 +10,7 @@ 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.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 @@ class FiveMDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]): 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 + except FiveMServerOfflineError as err: + raise UpdateFailed from err - 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) + players_list: list[str] = [] + for player in server.players: + players_list.append(player.name) + players_list.sort() - 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() - 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 + 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 @@ class FiveMEntity(CoordinatorEntity): 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 2e9ea834799..20ea057da6e 100644 --- a/homeassistant/components/fivem/binary_sensor.py +++ b/homeassistant/components/fivem/binary_sensor.py @@ -9,7 +9,7 @@ from homeassistant.core import HomeAssistant 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 @@ BINARY_SENSORS: tuple[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 792d5f1b025..e564faa81b7 100644 --- a/homeassistant/components/fivem/config_flow.py +++ b/homeassistant/components/fivem/config_flow.py @@ -8,8 +8,7 @@ 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.const import CONF_HOST, CONF_PORT from homeassistant.data_entry_flow import FlowResult import homeassistant.helpers.config_validation as cv @@ -21,22 +20,21 @@ 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: +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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 5907aa7300e..1676dc9f2b3 100644 --- a/homeassistant/components/fivem/const.py +++ b/homeassistant/components/fivem/const.py @@ -8,7 +8,6 @@ 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" diff --git a/homeassistant/components/fivem/strings.json b/homeassistant/components/fivem/strings.json index 2949dfb58ad..03afcc11f27 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 a81ce1e1ce5..8c4f7a54156 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 7af6e0fca48..a1a1e8f2a37 100644 --- a/tests/components/fivem/test_config_flow.py +++ b/tests/components/fivem/test_config_flow.py @@ -6,18 +6,17 @@ 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.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 f1e44e5a774..47a4f343d4f 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 d725f46bb50..4b0f67c5daf 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 7d59fc3c2b8..bc4ee48bb02 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 964f19e1d7c..eba2670aa95 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 e1001ab7b04..ca9e56f8efb 100644 --- a/homeassistant/components/mqtt/debug_info.py +++ b/homeassistant/components/mqtt/debug_info.py @@ -20,6 +20,11 @@ DATA_MQTT_DEBUG_INFO = "mqtt_debug_info" 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 722bfd51c9f..177c7d9b57f 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -573,7 +573,6 @@ class MqttDiscoveryUpdate(Entity): 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 @@ class MqttEntity( 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 3cb49598e8c..3d249fa2144 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 ec8ca77bded..3d96624e628 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 @@ class RenaultBinarySensor( 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 @@ BINARY_SENSOR_TYPES: tuple[RenaultBinarySensorEntityDescription, ...] = ( 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 d98b9864875..8e63d09b5c7 100644 --- a/homeassistant/components/renault/sensor.py +++ b/homeassistant/components/renault/sensor.py @@ -305,6 +305,24 @@ SENSOR_TYPES: tuple[RenaultSensorEntityDescription, ...] = ( 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 91704a59b51..90a1665b75b 100644 --- a/tests/components/renault/const.py +++ b/tests/components/renault/const.py @@ -54,6 +54,7 @@ FIXED_ATTRIBUTES = ( 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 @@ MOCK_VEHICLES = { "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 @@ MOCK_VEHICLES = { 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 @@ MOCK_VEHICLES = { 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 @@ MOCK_VEHICLES = { "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 @@ MOCK_VEHICLES = { 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 @@ MOCK_VEHICLES = { { 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 00000000000..a2ca08a71e9 --- /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 00000000000..81f20c4a46a --- /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 00000000000..d76e80a8b9c --- /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 38461bce859..c6c4d18d21d 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -52,7 +52,9 @@ from homeassistant.helpers.config_validation import ( # noqa: F401 ) 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 @@ class SensorEntity(Entity): 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 4857210f125..79d46f8ec2e 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 @@ STATE_EXPIRATION = timedelta(days=7) _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 @@ class RestoreStateData: 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 @@ class RestoreStateData: # 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 @@ class RestoreStateData: ) @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 @@ class RestoreStateData: 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 @@ class RestoreEntity(Entity): 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 @@ class RestoreEntity(Entity): 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 @@ class RestoreEntity(Entity): ) 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 3da0fcb98dd..c8dfb3ed841 100644 --- a/tests/common.py +++ b/tests/common.py @@ -44,7 +44,7 @@ from homeassistant.const import ( 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 eed88d92d04..df33cb1a081 100644 --- a/tests/components/sensor/test_init.py +++ b/tests/components/sensor/test_init.py @@ -11,10 +11,14 @@ from homeassistant.const import ( 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 79719b75326..efe951342fa 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 4ad2580ad8b..56587c80c34 100644 --- a/tests/testing_config/custom_components/test/sensor.py +++ b/tests/testing_config/custom_components/test/sensor.py @@ -5,6 +5,7 @@ Call init before using it in your tests to ensure clean test data. """ from homeassistant.components.sensor import ( DEVICE_CLASSES, + RestoreSensor, SensorDeviceClass, SensorEntity, ) @@ -109,3 +110,17 @@ class MockSensor(MockEntity, SensorEntity): 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 c118e56f9e3..f5f8875bb5f 100644 --- a/homeassistant/components/knx/schema.py +++ b/homeassistant/components/knx/schema.py @@ -35,7 +35,7 @@ from homeassistant.const import ( 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 d093fd23406..d659d7625c1 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -44,7 +44,7 @@ from homeassistant.helpers import ( 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 177c7d9b57f..fe989acb98e 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -34,11 +34,11 @@ from homeassistant.helpers.dispatcher import ( 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 @@ MQTT_ENTITY_COMMON_SCHEMA = MQTT_AVAILABILITY_SCHEMA.extend( { 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 a716e465450..b670c734b47 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -11,7 +11,7 @@ import logging 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 @@ SOURCE_PLATFORM_CONFIG = "platform_config" 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 1d28c50b9a1..6b7de074a24 100644 --- a/tests/helpers/test_entity.py +++ b/tests/helpers/test_entity.py @@ -6,6 +6,7 @@ import threading 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 | 122 ++++++++++++++++------ 2 files changed, 114 insertions(+), 52 deletions(-) diff --git a/homeassistant/components/deconz/number.py b/homeassistant/components/deconz/number.py index fff70b9f7b5..bf138aaef63 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 @@ from .gateway import DeconzGateway, get_gateway_from_config_entry @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 @@ async def async_setup_entry( 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 @@ class DeconzNumber(DeconzDevice, NumberEntity): 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 0cf0650e3d1..e0c469a1ba2 100644 --- a/tests/components/deconz/test_number.py +++ b/tests/components/deconz/test_number.py @@ -10,6 +10,8 @@ from homeassistant.components.number import ( 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, ) - await hass.config_entries.async_unload(config_entry.entry_id) + # Unload entry - assert hass.states.get("number.presence_sensor_delay").state == STATE_UNAVAILABLE + 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 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 | 359 ++++++++++-------- 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, 253 insertions(+), 189 deletions(-) diff --git a/homeassistant/components/spotify/__init__.py b/homeassistant/components/spotify/__init__.py index 5c36a0c71c3..a2a1fee50f2 100644 --- a/homeassistant/components/spotify/__init__.py +++ b/homeassistant/components/spotify/__init__.py @@ -4,7 +4,7 @@ import aiohttp 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 @@ CONFIG_SCHEMA = vol.Schema( 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 33e5e67a244..bd01fa64acb 100644 --- a/homeassistant/components/spotify/config_flow.py +++ b/homeassistant/components/spotify/config_flow.py @@ -8,6 +8,7 @@ from spotipy import Spotify 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 @@ class SpotifyFlowHandler( 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 @@ class SpotifyFlowHandler( 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 @@ class SpotifyFlowHandler( 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 e279b150883..bdb0ea8b959 100644 --- a/homeassistant/components/spotify/media_player.py +++ b/homeassistant/components/spotify/media_player.py @@ -6,6 +6,7 @@ import datetime as dt 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 @@ CONTENT_TYPE_MEDIA_CLASS = { "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 @@ class SpotifyMediaPlayer(MediaPlayerEntity): 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 @@ class SpotifyMediaPlayer(MediaPlayerEntity): @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 @@ class SpotifyMediaPlayer(MediaPlayerEntity): 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 @@ class SpotifyMediaPlayer(MediaPlayerEntity): @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 @@ class SpotifyMediaPlayer(MediaPlayerEntity): @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 @@ class SpotifyMediaPlayer(MediaPlayerEntity): @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 @@ class SpotifyMediaPlayer(MediaPlayerEntity): 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 @@ class SpotifyMediaPlayer(MediaPlayerEntity): _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 @@ class SpotifyMediaPlayer(MediaPlayerEntity): @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 @@ class SpotifyMediaPlayer(MediaPlayerEntity): 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 @@ class SpotifyMediaPlayer(MediaPlayerEntity): 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"], - children_media_class=media_class["children"], - media_content_id=media_content_id, - media_content_type=MEDIA_PLAYER_PREFIX + media_content_type, - can_play=False, can_expand=True, - children=[], + 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"] @@ -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, - children_media_class=MEDIA_CLASS_TRACK, - media_content_id=item_id, - media_content_type=MEDIA_PLAYER_PREFIX + "category_playlists", - thumbnail=fetch_image_url(item, key="icons"), - can_play=False, 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.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 f775e5df85d..caec5b8a288 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 96d58927959..31788f3643d 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 6b14803b499..5e3d9bd4b11 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -188,8 +188,6 @@ IGNORED_MODULES: Final[list[str]] = [ "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 fb0279f9112..a1a77da1d10 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 27ad691e55e..fa5a1d09920 100644 --- a/homeassistant/components/plugwise/binary_sensor.py +++ b/homeassistant/components/plugwise/binary_sensor.py @@ -143,4 +143,4 @@ class PlugwiseNotifyBinarySensorEntity(PlugwiseBinarySensorEntity): 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 | 65 +++-------- .../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, 432 insertions(+), 109 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 110cf11cde9..a2eb70e1c42 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -4,7 +4,7 @@ from __future__ import annotations 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.http import KEY_AUTHENTICATED, HomeAssistantView 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 @@ from homeassistant.helpers.config_validation import ( # noqa: F401 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) + url = f"{get_url(hass)}{url}" - 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, - ) + 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 00000000000..841a9365320 --- /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 d418373e599..1354c5c00fb 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -18,6 +18,7 @@ from pychromecast.socket_client import ( 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 import config_validation as cv 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 @@ class CastDevice(MediaPlayerEntity): 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 @@ class CastDevice(MediaPlayerEntity): 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 0c52012e43c..9d7780ab900 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 bd3841cc4e8..ee2a3cb2974 100644 --- a/tests/components/camera/common.py +++ b/tests/components/camera/common.py @@ -8,6 +8,7 @@ from unittest.mock import Mock 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 00000000000..b09f7696ef2 --- /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 403cacec1f1..0e53e163404 100644 --- a/tests/components/camera/test_init.py +++ b/tests/components/camera/test_init.py @@ -8,11 +8,7 @@ from unittest.mock import Mock, PropertyMock, mock_open, patch 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.const import ( 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 00000000000..d5d65296e65 --- /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 51fe4a086a6..36ee2ce818a 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -10,6 +10,7 @@ import attr 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 @@ from homeassistant.const import ( 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 7e9086f4467..15a9b8d1ff8 100644 --- a/tests/helpers/test_network.py +++ b/tests/helpers/test_network.py @@ -13,6 +13,7 @@ from homeassistant.helpers.network import ( _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 b6084d148a3..b4aaad38618 100644 --- a/homeassistant/components/generic/camera.py +++ b/homeassistant/components/generic/camera.py @@ -96,7 +96,7 @@ class GenericCamera(Camera): 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 60e68a1e7b1..042cd2ee650 100644 --- a/tests/components/generic/test_camera.py +++ b/tests/components/generic/test_camera.py @@ -9,6 +9,7 @@ import pytest 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 c9f7d19a96a..6ab485f534f 100644 --- a/homeassistant/components/homewizard/config_flow.py +++ b/homeassistant/components/homewizard/config_flow.py @@ -14,7 +14,14 @@ from homeassistant.components import persistent_notification, zeroconf 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): # 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) -> 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 9a6c465532f..75c522a211e 100644 --- a/homeassistant/components/homewizard/const.py +++ b/homeassistant/components/homewizard/const.py @@ -14,11 +14,13 @@ DOMAIN = "homewizard" 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 f416027da4a..061b7b634d4 100644 --- a/tests/components/homewizard/test_config_flow.py +++ b/tests/components/homewizard/test_config_flow.py @@ -8,7 +8,11 @@ from homeassistant import config_entries 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={} + 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={"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 | 32 +++++++- .../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, 1133 insertions(+), 89 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 d17b0f33c2a..7fa1e0d7c46 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 3d961086dfc..79c2222a6da 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 e49e9202dbe..3701a49ccfb 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 3667c2432bf..c442cda7468 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 1ee479c51da..37ac0f7d876 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 298163c3d41..bf0ae52b106 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 1f01a9d69c9..e93e71d9e26 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 c90ed20af74..2ca14c224d9 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 8e3dc496a29..23313013af4 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 5a7d7a4bff0..f67e5bbfbee 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 ce766c314ed..2317e475a37 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 137f781fd05..cf318a626c9 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 238f1c3d30e..3b1993f3fed 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 7ced75e0a2b..d874763045f 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 e7ddd3cf9ee..782906fac0a 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 a2dea3c10cf..c5f8f422b27 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 b9c0322af20..62900716f62 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 4178ce86cb6..bbf0437ba06 100644 --- a/homeassistant/components/elkm1/translations/pt-BR.json +++ b/homeassistant/components/elkm1/translations/pt-BR.json @@ -2,25 +2,51 @@ "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": { - "user": { + "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": "Usu\u00e1rio" + "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": "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 954722ecf52..1b4bf7250d7 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 0a2f1f60faa..7b25413c6fc 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 8cbf872fb9c..9e4f6b45341 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 00000000000..d269046fe31 --- /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 00000000000..dfff0f0ce93 --- /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 00000000000..5c6852a126e --- /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 8c4f7a54156..e07c0666e24 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 00000000000..b0c2bce602d --- /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 00000000000..3cf44f86f5d --- /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 00000000000..36bc21ec26d --- /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 00000000000..592db241d21 --- /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 00000000000..2b179dda182 --- /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 00000000000..fdc2d8b49ef --- /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 c504e31051a..109c0b58c70 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 33d580d03f8..93c2616be99 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 79f1444ff44..857958a8490 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 f868f770c5d..beb5447f89c 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 851cac800e6..5faedff893b 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 52dc19b164d..745f07bdadb 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 00000000000..0a59529b6ba --- /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 881521a2c29..4388fa7d517 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 34e67bcf73e..5c82a1e944b 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 d8d7bff1585..21144ed7829 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 00000000000..d455990b1f0 --- /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 3a870e47986..c533197ca84 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 00000000000..b7ab8eebbaf --- /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 f1021cbd6e9..271cba9e0c4 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 17770f9e1dd..8a30af3fd59 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 3a870e47986..a6b60231a85 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 c1a3a0a8084..375cf8a8ce3 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 f91afa32f74..2eeb40aee6b 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 60fc0714228..7045a0a3dbe 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 931cd7a8167..dcd95ab75ca 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 2a70661d788..08c664a909d 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 31da1a72f64..e67dddbb0b4 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 f5feb67cfe1..0f3a6ad3cde 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 38e69556faa..84d09f9116b 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 3bbdb67e6ad..becbae65f9e 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 b2bf92f329f..bbf2b15d13e 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 7705581178b..6881d7edb5b 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 d351645cebd..611c4784902 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 00769a3799e..0989aec66fe 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 f51b432b642..efe4f588a71 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 6b942c21a9a..0633c0d8424 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 371f1ce022e..04c2fc2b07a 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 98c8779fe1f..a54a366849d 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 ddfb779ea2e..af94e340408 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 cef3726d759..d5c10289c09 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 8d4f82fa14e..9c56edf13b9 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 2724fdd05f2..2aaaf6dbd01 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 3a870e47986..105e4072300 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 34307edfce0..25f3ffe785f 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 5d3b29018dd..9aef5e20dbd 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 f348b97d111..a5fb61a685b 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 39f0e35a93b..05c0a69708c 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 e7ab7918fb3..4c80d089cbe 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 f1b30177d5a..18e9156a936 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 00000000000..e92874775ad --- /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 3ade230c467..46fc072d9fe 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 4fb5542f510..9132a6c6b9e 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 8169a6be4bf..7f0242fedd0 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 06a21cfdf18..9e285f65f84 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 619f6e57192..0c0cb4249d1 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 ce690131579..28b62967894 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 4ea665e4a79..9b404daf612 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 be98268cf54..d75bbd8a8be 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 0697075be00..c841d5faf3b 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 aecb556cf17..317a2763152 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 090849227f8..0529ebebba0 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 e6be0a02aed..e0ece3f3042 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 7cebe87febb..330bcf6ce11 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 3612367090e..b2f8e2541fe 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 0ea84e9d658..72460919989 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 00000000000..9d66fde17f3 --- /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 00000000000..d1002376d82 --- /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 6f974191dc7..97929946536 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 621be0d70cc..f0624f627bb 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 00000000000..54a7c553a53 --- /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 00000000000..ffd1a983989 --- /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 6ec2488368b..7a3523ede16 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 47c1d650fa5..16f679e9c7d 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 9e7932b2dd7..3ceb09bf90a 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 31897a0d3c9..de49d07361a 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 00000000000..02c83a6e916 --- /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 ba91812d06f..a382cef5494 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 00000000000..5afd788a3fb --- /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 00000000000..da9ddeb6d67 --- /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 00000000000..f46f1f6c452 --- /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 00000000000..d75251367cd --- /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 00000000000..d24b2f7327a --- /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 00000000000..ee6eb1e9ae7 --- /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 d6ad5c9cd13..4011a420ab1 100644 --- a/homeassistant/components/roku/media_player.py +++ b/homeassistant/components/roku/media_player.py @@ -7,6 +7,7 @@ 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 @@ -46,7 +47,7 @@ from homeassistant.const import ( 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 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity): 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 11930aada99..2bca965c3eb 100644 --- a/homeassistant/components/vlc_telnet/media_player.py +++ b/homeassistant/components/vlc_telnet/media_player.py @@ -10,6 +10,7 @@ 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 @@ -32,10 +33,11 @@ from homeassistant.components.media_player.const import ( 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 @@ class VlcDevice(MediaPlayerEntity): # 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 19468fcf686..d66c56d208f 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 4b0f67c5daf..960ee057f29 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 bc4ee48bb02..04ef6c9ea9f 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 4a4ffe6eb0a..deb9577d576 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 960ee057f29..cefeb4aa528 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 04ef6c9ea9f..98fca3a0c88 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 eba2670aa95..b797491b7a9 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 79acc54b9b2..4886bbac621 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -158,7 +158,8 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator): 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 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator): 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 f35eca6b914..fa4ff6a7db8 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 59cb75901a2..d168878c5da 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 f1a90651716..e0d1b797bb3 100644 --- a/homeassistant/components/velbus/light.py +++ b/homeassistant/components/velbus/light.py @@ -22,7 +22,7 @@ from homeassistant.components.light import ( ) 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 54a0735186f..e27c7475251 100644 --- a/homeassistant/components/synology_dsm/common.py +++ b/homeassistant/components/synology_dsm/common.py @@ -33,7 +33,7 @@ from homeassistant.const import ( ) 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 @@ class SynoApi: ) 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 @@ class SynoApi: 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 f26a43b2ca0..a7a336e0c1b 100644 --- a/homeassistant/components/synology_dsm/service.py +++ b/homeassistant/components/synology_dsm/service.py @@ -15,7 +15,6 @@ from .const import ( SERVICE_SHUTDOWN, SERVICES, SYNO_API, - SYSTEM_LOADED, ) LOGGER = logging.getLogger(__name__) @@ -57,7 +56,6 @@ async def async_setup_services(hass: HomeAssistant) -> 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 fa5a1d09920..c022f209c0a 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.core import HomeAssistant, callback 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 @@ class PlugwiseBinarySensorEntity(PlugwiseEntity, BinarySensorEntity): 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 5ecab66bbde..b3e7472369c 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -76,8 +76,6 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): 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 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): 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 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): # 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 6f590d410fd..5a7bec89e31 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 cefeb4aa528..81942492a4c 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 e0d1b797bb3..2926b30c22f 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 5fa28541815..349ee6d05e9 100644 --- a/homeassistant/components/plugwise/entity.py +++ b/homeassistant/components/plugwise/entity.py @@ -48,6 +48,11 @@ class PlugwiseEntity(CoordinatorEntity[PlugwiseData]): } ) + @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 a3f2dfb4c6f..06fb0760818 100644 --- a/homeassistant/helpers/location.py +++ b/homeassistant/helpers/location.py @@ -4,8 +4,6 @@ from __future__ import annotations 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. - # Check if the entity has location attributes + 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_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 219d015bdf7..5ae1891e45a 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 a98f75fceb8..a4c550b8119 100644 --- a/homeassistant/components/group/cover.py +++ b/homeassistant/components/group/cover.py @@ -57,6 +57,8 @@ KEY_POSITION = "position" 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 cef30dc3c69..7920e0f5d20 100644 --- a/homeassistant/components/group/fan.py +++ b/homeassistant/components/group/fan.py @@ -52,6 +52,8 @@ SUPPORTED_FLAGS = {SUPPORT_SET_SPEED, SUPPORT_DIRECTION, SUPPORT_OSCILLATE} 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 201156db600..ea74136b204 100644 --- a/homeassistant/components/group/light.py +++ b/homeassistant/components/group/light.py @@ -58,6 +58,9 @@ from .util import find_state_attributes, mean_tuple, reduce_attribute 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 cf1fba992e7..d090141a9d2 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 abb1dcf245a..19b4fe4670a 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 843f15c7113..d356b20b40f 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 27962297952..f741e2d1a84 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 3d4851d8852..056eacb6a5b 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 81c85a93056..72a36076bf4 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 81942492a4c..5fa50cde842 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 98fca3a0c88..4a74f1de59c 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 d8d0a040d5f..62eabd8b0de 100644 --- a/homeassistant/components/plugwise/const.py +++ b/homeassistant/components/plugwise/const.py @@ -62,6 +62,5 @@ FLAME_ICON = "mdi:fire" 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 6e518aad490..05ef937c6fd 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 @@ from .coordinator import PlugwiseDataUpdateCoordinator 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 98ba1cd8183..c95474a2b5a 100644 --- a/homeassistant/components/plugwise/switch.py +++ b/homeassistant/components/plugwise/switch.py @@ -5,15 +5,23 @@ from typing import Any 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 @@ class PlugwiseSwitchEntity(PlugwiseEntity, SwitchEntity): """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 @@ class PlugwiseSwitchEntity(PlugwiseEntity, SwitchEntity): 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 1e1fb4a0679..4d09489944d 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 | 84 +++++++------------ homeassistant/components/plugwise/const.py | 2 - 2 files changed, 30 insertions(+), 56 deletions(-) diff --git a/homeassistant/components/plugwise/binary_sensor.py b/homeassistant/components/plugwise/binary_sensor.py index c022f209c0a..f6118dde370 100644 --- a/homeassistant/components/plugwise/binary_sensor.py +++ b/homeassistant/components/plugwise/binary_sensor.py @@ -12,7 +12,7 @@ from homeassistant.core import HomeAssistant, callback 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 @@ BINARY_SENSORS: tuple[PlugwiseBinarySensorEntityDescription, ...] = ( 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 + 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, - ) - ) - - 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 @@ class PlugwiseBinarySensorEntity(PlugwiseEntity, BinarySensorEntity): 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() - - -class PlugwiseNotifyBinarySensorEntity(PlugwiseBinarySensorEntity): - """Representation of a Plugwise Notification binary_sensor.""" - - @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() diff --git a/homeassistant/components/plugwise/const.py b/homeassistant/components/plugwise/const.py index 62eabd8b0de..506f20c2768 100644 --- a/homeassistant/components/plugwise/const.py +++ b/homeassistant/components/plugwise/const.py @@ -62,5 +62,3 @@ FLAME_ICON = "mdi:fire" 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 d2f630a8b6d..15fc6634f5b 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 887c0517d05..2cf7005cb66 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 e9e54a688c4..3f665e475f0 100644 --- a/homeassistant/components/config/core.py +++ b/homeassistant/components/config/core.py @@ -35,7 +35,6 @@ class CheckConfigView(HomeAssistantView): @websocket_api.require_admin -@websocket_api.async_response @websocket_api.websocket_command( { "type": "config/core/update", @@ -50,6 +49,7 @@ class CheckConfigView(HomeAssistantView): 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 26a2d930d18..f5ffc574b86 100644 --- a/homeassistant/components/config/entity_registry.py +++ b/homeassistant/components/config/entity_registry.py @@ -4,15 +4,12 @@ import voluptuous as vol 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 31788f3643d..e2b406301da 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 5e3d9bd4b11..19bd3ee77f3 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -21,10 +21,6 @@ IGNORED_MODULES: Final[list[str]] = [ "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 9b753284dc5..3fe3b9071b0 100644 --- a/homeassistant/components/wiz/config_flow.py +++ b/homeassistant/components/wiz/config_flow.py @@ -13,6 +13,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.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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 288fd76acc4..548e79e9157 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 5747182a231..87a89822641 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 a52ca323830..28645e53e14 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 12df703a480..18af5021544 100644 --- a/homeassistant/components/nina/const.py +++ b/homeassistant/components/nina/const.py @@ -26,7 +26,7 @@ CONST_LIST_E_TO_H: list[str] = ["E", "F", "G", "H"] 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 1354c5c00fb..29fa38fbdc6 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -3,10 +3,9 @@ from __future__ import annotations 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 voluptuous as vol 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 @@ class CastDevice(MediaPlayerEntity): 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 @@ class CastDevice(MediaPlayerEntity): }, } - 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 587c75dd035..2de42c05dde 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -59,7 +59,6 @@ import homeassistant.helpers.config_validation as cv 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.network import get_url 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 @@ from .const import ( 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 00000000000..829c96671a9 --- /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 67f4331aa60..e7b16f6ac88 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 2374eca2e6a..81c629529df 100644 --- a/homeassistant/components/media_source/__init__.py +++ b/homeassistant/components/media_source/__init__.py @@ -12,6 +12,7 @@ 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, ) @@ -28,8 +29,6 @@ from .const import DOMAIN, URI_SCHEME, URI_SCHEME_REGEX 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 4011a420ab1..48b85f5912c 100644 --- a/homeassistant/components/roku/media_player.py +++ b/homeassistant/components/roku/media_player.py @@ -4,17 +4,15 @@ from __future__ import annotations 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.const import ( 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 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity): 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 5f5220cd164..e7ee76070a1 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -6,7 +6,6 @@ import datetime 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 @@ from soco.data_structures import DidlFavorite 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.exceptions import HomeAssistantError 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 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity): 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 2bca965c3eb..140c2b2c253 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.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, 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 @@ class VlcDevice(MediaPlayerEntity): 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 00000000000..ba7a93fc3a3 --- /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 61dccdb2d8b..b64c08cc1af 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 a2a1fee50f2..5599965a2a6 100644 --- a/homeassistant/components/spotify/__init__.py +++ b/homeassistant/components/spotify/__init__.py @@ -4,7 +4,6 @@ import aiohttp 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.config_entry_oauth2_flow import ( 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 @@ CONFIG_SCHEMA = vol.Schema( 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 00000000000..0ffdfd6ace6 --- /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 7978ac8712f..6e54ed21ec1 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 @@ SPOTIFY_SCOPES = [ ] 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 bdb0ea8b959..58f581fdf82 100644 --- a/homeassistant/components/spotify/media_player.py +++ b/homeassistant/components/spotify/media_player.py @@ -4,7 +4,6 @@ from __future__ import annotations 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 @@ import requests 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 @@ from homeassistant.components.media_player.const import ( 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 import DeviceInfo 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 @@ REPEAT_MODE_MAPPING_TO_SPOTIFY = { 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 @@ class SpotifyMediaPlayer(MediaPlayerEntity): 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 00000000000..cdb8e933523 --- /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 b64c08cc1af..b4b84dd4025 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 ec0b437186e..7c259adbfa3 100644 --- a/script/hassfest/coverage.py +++ b/script/hassfest/coverage.py @@ -20,46 +20,13 @@ DONT_IGNORE = ( # 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 b5deb8517bb..7d7bc7695cd 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 @@ CONFIG_SCHEMA = vol.Schema( 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 00000000000..b3313bba9dd --- /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 77f36cf44de..65e5576f9d7 100644 --- a/homeassistant/components/freebox/const.py +++ b/homeassistant/components/freebox/const.py @@ -17,7 +17,7 @@ APP_DESC = { } 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 00000000000..0a6625e163a --- /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 c8cc32b2d35..64b450a14c7 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 5b353141215..4b2693374f6 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 64b450a14c7..1becdf7a502 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 1a08c5c4703..24e52d3186e 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 7741ec36708..71068479fe4 100644 --- a/homeassistant/components/sonos/__init__.py +++ b/homeassistant/components/sonos/__init__.py @@ -343,7 +343,6 @@ class SonosDiscoveryManager: ) ) 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 b4b84dd4025..7e3bd6c1e50 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 370e56feeeb..60a9163df5f 100644 --- a/homeassistant/components/overkiz/const.py +++ b/homeassistant/components/overkiz/const.py @@ -27,6 +27,7 @@ PLATFORMS: list[Platform] = [ Platform.NUMBER, Platform.SCENE, Platform.SENSOR, + Platform.SIREN, Platform.SWITCH, ] @@ -36,7 +37,7 @@ IGNORED_OVERKIZ_DEVICES: list[UIClass | UIWidget] = [ ] # 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 @@ OVERKIZ_DEVICE_TO_PLATFORM: dict[UIClass | UIWidget, Platform] = { 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 @@ OVERKIZ_DEVICE_TO_PLATFORM: dict[UIClass | UIWidget, Platform] = { 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 00000000000..8f9d2d6167f --- /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 1bc473a6001..46fbbc3d972 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 dfe8209fa1c..11e196e75f2 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 5ef5b36c80d..780b8f81753 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 4a39ef36f3c..83f1ab9c5eb 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 00000000000..8fd587a0640 --- /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 b22ba8c8528..fa05ab5d436 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 d269046fe31..98f8a2a7d26 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 dfff0f0ce93..8e2a192a18c 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 b0c2bce602d..0ca491bd232 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 00000000000..0128d1fbeca --- /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 00000000000..b7c45c6bad1 --- /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 00000000000..b932679af93 --- /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 fdc2d8b49ef..3b1527f7a35 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 4e5677f124a..9eb419288a8 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 5dd3cb32bf4..cf009baed45 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 17f42da5805..7bf46c79e92 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 df027146b12..6a5606b05f4 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 5947717e69b..2337bbd4216 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 90e3ccd3c17..489f29db7a6 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 c76f2fd9cf2..df40631e6d9 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 78747578d0e..1d8befd56c9 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 0d105c59c05..9ab8349670e 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 8235fcc0ce9..15237e3a16a 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 00000000000..c7296d1b565 --- /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 bb4d9f9907e..6b29911b28a 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 a228f0c0506..00898513f1f 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 cb5016cc230..0c74eb8b640 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 5e6b186e38c..ca42365d84b 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 d5c10289c09..12186a54eec 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 f323efbabff..889356fe8a1 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 9b397138c52..cee740b4d50 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 1247b8be42b..c91e0bc34e8 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 77098f93470..9d2ac87d833 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 6d41904836a..f89c85eeaee 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 28b62967894..12be88bbd8c 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 a7b7bb272dd..f8cc5ffcb57 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 4163b736ac8..72e385606c9 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 1de3cdddabd..e84a9319f75 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 66b7fac78bd..c5ddc019984 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 54a7c553a53..f9a0ae7bd8c 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 505788115c9..2c244b1bfc0 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 87a89822641..ab4b7929739 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 c31366f17d3..f398c1a49bd 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 ffd1a983989..e99e8e7e14c 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 4ede47e2a32..3b0781bbc73 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 a1163f5cf02..7d5485e354e 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 35bdecea082..b643f7df4de 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 02c83a6e916..34c3f5b9499 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 00000000000..d60ac60d899 --- /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 b9844303956..ed530704550 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -175,7 +175,7 @@ class SonosSpeaker: # 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 @@ class SonosSpeaker: # # 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 @@ class SonosSpeaker: 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 @@ class SonosSpeaker: 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 @@ class SonosSpeaker: 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 af923538fb2..8c4f483b9a8 100644 --- a/homeassistant/components/tradfri/base_class.py +++ b/homeassistant/components/tradfri/base_class.py @@ -84,7 +84,7 @@ class TradfriBaseEntity(CoordinatorEntity): 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 7e3bd6c1e50..d3c5094d4b1 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 1becdf7a502..20fe7d086e0 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 00000000000..62e18917dc6 --- /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 00000000000..da536c4bd06 --- /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 00000000000..cafdca040b3 --- /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 00000000000..268936982bd --- /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 00000000000..b755b28f826 --- /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 00000000000..3347b2f318c --- /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 2aab7c81481..be18826bedb 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -199,6 +199,7 @@ FLOWS = [ "mobile_app", "modem_callerid", "modern_forms", + "moehlenhoff_alpha2", "monoprice", "motion_blinds", "motioneye", diff --git a/requirements_all.txt b/requirements_all.txt index 5fa50cde842..01011b048b7 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 4a74f1de59c..8f57bc2b05e 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 00000000000..76bd1fd00aa --- /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 00000000000..ccfa98718e5 --- /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 90c2c81e552..3b5b994282d 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 01011b048b7..399d5cdb0e7 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 8f57bc2b05e..8bb1ac39ed3 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 1efefb62109..39eb1190388 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 399d5cdb0e7..7245f7970fd 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 8bb1ac39ed3..3b024a1ac32 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 10088f62414..7fb785d429e 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -446,9 +446,7 @@ async def async_setup_entry( # noqa: C901 # 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 87e9f3adbbd..a61fc3765c7 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -48,7 +48,7 @@ class ZWaveBaseEntity(Entity): # 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 de7ed5da502..3deb75cf761 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 d9b46ac725c..73a094fd95a 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 90a19526c7c..9b84b9a54c2 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 eeb072e3a62..ebb986d6a7e 100644 --- a/homeassistant/components/tibber/sensor.py +++ b/homeassistant/components/tibber/sensor.py @@ -210,7 +210,7 @@ SENSORS: tuple[SensorEntityDescription, ...] = ( ), 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 @@ class TibberDataCoordinator(update_coordinator.DataUpdateCoordinator): 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 7245f7970fd..5376c7a5f11 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 3b024a1ac32..ae3fe7eb5c4 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 29fa38fbdc6..565961e1b8c 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -473,13 +473,13 @@ class CastDevice(MediaPlayerEntity): 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 @@ class CastDevice(MediaPlayerEntity): 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 8ebf87b98d5..ceb57ef1fb4 100644 --- a/homeassistant/components/media_source/models.py +++ b/homeassistant/components/media_source/models.py @@ -64,19 +64,22 @@ class MediaSourceItem: 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 3ad84825090..abee8310e17 100644 --- a/homeassistant/components/demo/__init__.py +++ b/homeassistant/components/demo/__init__.py @@ -3,7 +3,7 @@ import asyncio 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 e2b406301da..c5afffd5638 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 19bd3ee77f3..376f33de65b 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -38,7 +38,6 @@ IGNORED_MODULES: Final[list[str]] = [ "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 150ce3a7eb6..17daaaf08ee 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -35,7 +35,7 @@ from homeassistant.util import dt as dt_util 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_OFF = "OFF" 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 91d7cdd772d..2b4e03f8663 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -64,7 +64,7 @@ from . import ( 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_MAX = "max_temp" 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 0feb21b0010..46396820545 100644 --- a/homeassistant/components/mqtt/const.py +++ b/homeassistant/components/mqtt/const.py @@ -18,6 +18,7 @@ CONF_ENCODING = "encoding" 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 6fe36cd5fcd..114c8f5729f 100644 --- a/homeassistant/components/mqtt/fan.py +++ b/homeassistant/components/mqtt/fan.py @@ -49,12 +49,13 @@ from .const import ( 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 @@ DEFAULT_SPEED_RANGE_MAX = 100 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 c9a5341a43f..0ab99cf0914 100644 --- a/homeassistant/components/mqtt/humidifier.py +++ b/homeassistant/components/mqtt/humidifier.py @@ -39,7 +39,9 @@ from .const import ( 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_TOPIC = "mode_state_topic" 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_ON = "ON" 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 1ff08a49ab1..d5665a1beff 100644 --- a/homeassistant/components/mqtt/light/schema_basic.py +++ b/homeassistant/components/mqtt/light/schema_basic.py @@ -59,6 +59,8 @@ from ..const import ( 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_TEMPLATE = "rgbww_command_template" 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_STATE_TOPIC = "white_value_state_topic" 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 52b840fcfaf..83d40ed5aae 100644 --- a/homeassistant/components/mqtt/light/schema_json.py +++ b/homeassistant/components/mqtt/light/schema_json.py @@ -100,8 +100,6 @@ CONF_FLASH_TIME_SHORT = "flash_time_short" 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 5700a8ab868..4f25bde928d 100644 --- a/homeassistant/components/mqtt/light/schema_template.py +++ b/homeassistant/components/mqtt/light/schema_template.py @@ -41,6 +41,7 @@ from ..const import ( 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_MIN_MIREDS = "min_mireds" 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 b3c5df157c9..e43edb12873 100644 --- a/homeassistant/components/mqtt/siren.py +++ b/homeassistant/components/mqtt/siren.py @@ -48,6 +48,7 @@ from .const import ( CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, + CONF_STATE_VALUE_TEMPLATE, DOMAIN, PAYLOAD_EMPTY_JSON, PAYLOAD_NONE, @@ -66,7 +67,6 @@ 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" diff --git a/homeassistant/components/mqtt/switch.py b/homeassistant/components/mqtt/switch.py index 906901b6080..f0ec18cc379 100644 --- a/homeassistant/components/mqtt/switch.py +++ b/homeassistant/components/mqtt/switch.py @@ -33,6 +33,7 @@ from .const import ( 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 @@ DEFAULT_OPTIMISTIC = False 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 | 85 ++++++++++------ 5 files changed, 178 insertions(+), 148 deletions(-) create mode 100644 homeassistant/components/plugwise/util.py diff --git a/homeassistant/components/plugwise/climate.py b/homeassistant/components/plugwise/climate.py index b3e7472369c..4cea3d9499e 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.const import ATTR_TEMPERATURE, TEMP_CELSIUS 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 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): 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 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): 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 c95474a2b5a..5365d5834a8 100644 --- a/homeassistant/components/plugwise/switch.py +++ b/homeassistant/components/plugwise/switch.py @@ -3,8 +3,6 @@ from __future__ import annotations 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 homeassistant.helpers.entity_platform import AddEntitiesCallback 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 @@ class PlugwiseSwitchEntity(PlugwiseEntity, SwitchEntity): 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 00000000000..58c7715815e --- /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 a7af9f9c0c9..5eb60ebeb6f 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 @@ from homeassistant.components.climate.const import ( 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 4d09489944d..2816b2dece6 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, - ) - state = hass.states.get("switch.cv_pomp") - assert str(state.state) == "on" + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + "switch", + "turn_off", + {"entity_id": "switch.cv_pomp"}, + blocking=True, + ) - await hass.services.async_call( - "switch", - "turn_on", - {"entity_id": "switch.fibaro_hc2"}, - 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" + ) + + 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 89256c95468..e70f5efc626 100644 --- a/homeassistant/components/demo/fan.py +++ b/homeassistant/components/demo/fan.py @@ -114,11 +114,11 @@ class BaseDemoFan(FanEntity): 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 @@ class BaseDemoFan(FanEntity): 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 @@ class AsyncDemoPercentageFan(BaseDemoFan, FanEntity): 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 13e15eb274e..9bb3a30686d 100644 --- a/homeassistant/components/demo/light.py +++ b/homeassistant/components/demo/light.py @@ -195,17 +195,17 @@ class DemoLight(LightEntity): 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 dd22c5f9827..8660604af9e 100644 --- a/homeassistant/components/demo/number.py +++ b/homeassistant/components/demo/number.py @@ -105,13 +105,10 @@ class DemoNumber(NumberEntity): 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 2e06b009c9c..f2c1ce11b0a 100644 --- a/homeassistant/components/demo/remote.py +++ b/homeassistant/components/demo/remote.py @@ -46,7 +46,7 @@ class DemoRemote(RemoteEntity): 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 32d3de4b497..9cf18a4c902 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 c61e39d085b..217119e9372 100644 --- a/homeassistant/components/demo/switch.py +++ b/homeassistant/components/demo/switch.py @@ -64,12 +64,8 @@ class DemoSwitch(SwitchEntity): 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 c5afffd5638..2ad49bfdc4c 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 376f33de65b..f776f1b635b 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -38,12 +38,6 @@ IGNORED_MODULES: Final[list[str]] = [ "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 349ee6d05e9..d57681ae504 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 @@ class PlugwiseEntity(CoordinatorEntity[PlugwiseData]): """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 5365d5834a8..e7fb0b6f3db 100644 --- a/homeassistant/components/plugwise/switch.py +++ b/homeassistant/components/plugwise/switch.py @@ -5,10 +5,10 @@ from typing import Any 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 @@ class PlugwiseSwitchEntity(PlugwiseEntity, SwitchEntity): 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 @@ class PlugwiseSwitchEntity(PlugwiseEntity, SwitchEntity): """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 2816b2dece6..4785a222f69 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 318b457c24b..a1231708b91 100644 --- a/homeassistant/components/template/__init__.py +++ b/homeassistant/components/template/__init__.py @@ -61,7 +61,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: 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 @@ class TriggerUpdateCoordinator(update_coordinator.DataUpdateCoordinator): 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 d2578468886..4c080c736d0 100644 --- a/homeassistant/components/template/binary_sensor.py +++ b/homeassistant/components/template/binary_sensor.py @@ -31,7 +31,7 @@ from homeassistant.const import ( 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 @@ class TriggerBinarySensorEntity(TriggerEntity, BinarySensorEntity): 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 86f481d7032..d6f41649734 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 @@ class TemplateButtonEntity(TemplateEntity, ButtonEntity): ) -> 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 9d0a32f3edd..1ddd37ba7bc 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_DIRECTION_ACTION = "set_direction" 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 @@ class TemplateFan(TemplateEntity, FanEntity): 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 @@ class TemplateFan(TemplateEntity, FanEntity): 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 036485cd363..89adb45a6d0 100644 --- a/homeassistant/components/template/number.py +++ b/homeassistant/components/template/number.py @@ -108,6 +108,7 @@ class TemplateNumber(TemplateEntity, NumberEntity): ) -> 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 28295835318..fa2668c1e81 100644 --- a/homeassistant/components/template/select.py +++ b/homeassistant/components/template/select.py @@ -102,13 +102,14 @@ class TemplateSelect(TemplateEntity, SelectEntity): ) -> 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 ee4b6244757..5e592e5d717 100644 --- a/homeassistant/components/template/template_entity.py +++ b/homeassistant/components/template/template_entity.py @@ -16,13 +16,13 @@ from homeassistant.const import ( 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 @@ LEGACY_FIELDS = { 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 @@ class _TemplateAttribute: 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 @@ class _TemplateAttribute: 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 @@ class TemplateEntity(Entity): """ 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 @@ class TemplateEntity(Entity): 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 d4bc96e43bf..2768609e65d 100644 --- a/homeassistant/components/template/trigger_entity.py +++ b/homeassistant/components/template/trigger_entity.py @@ -12,6 +12,7 @@ from homeassistant.const import ( 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 @@ class TriggerEntity(update_coordinator.CoordinatorEntity): 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 @@ class TriggerEntity(update_coordinator.CoordinatorEntity): 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 @@ class TriggerEntity(update_coordinator.CoordinatorEntity): ) 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 2ad49bfdc4c..d4db04e8e82 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 f776f1b635b..99104702f26 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -179,16 +179,8 @@ IGNORED_MODULES: Final[list[str]] = [ "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 0af31b47730..f5cef1e7484 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -36,6 +36,7 @@ if TYPE_CHECKING: _LOGGER = logging.getLogger(__name__) +SOURCE_DHCP = "dhcp" SOURCE_DISCOVERY = "discovery" SOURCE_HASSIO = "hassio" SOURCE_HOMEKIT = "homekit" @@ -46,7 +47,6 @@ SOURCE_SSDP = "ssdp" 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 @@ class ConfigEntryState(Enum): 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 | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/plugwise/binary_sensor.py b/homeassistant/components/plugwise/binary_sensor.py index f6118dde370..c9053e8468e 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 @@ class PlugwiseBinarySensorEntity(PlugwiseEntity, BinarySensorEntity): 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: - """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 + @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) - 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 + @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 - # 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 - } + @property + def extra_state_attributes(self) -> Mapping[str, Any] | None: + """Return entity specific state attributes.""" + if self.entity_description.key != "plugwise_notification": + return None - 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) + 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) - super()._handle_coordinator_update() + 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 e7fb0b6f3db..45a10297ed5 100644 --- a/homeassistant/components/plugwise/switch.py +++ b/homeassistant/components/plugwise/switch.py @@ -3,9 +3,14 @@ from __future__ import annotations 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 .entity import PlugwiseEntity 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 506f20c2768..1beeff501b0 100644 --- a/homeassistant/components/plugwise/const.py +++ b/homeassistant/components/plugwise/const.py @@ -39,26 +39,10 @@ ZEROCONF_MAP = { # 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 fa516c88c6c..92867a3dfe0 100644 --- a/homeassistant/components/plugwise/sensor.py +++ b/homeassistant/components/plugwise/sensor.py @@ -20,7 +20,7 @@ from homeassistant.const import ( 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 @@ class PlugwiseSensorEnity(PlugwiseEntity, SensorEntity): 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 @@ class PlugwiseSensorEnity(PlugwiseEntity, SensorEntity): 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 | 94 +++++++------- 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, 281 insertions(+), 120 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 2c4485c5b72..d0473c4f5c3 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 .const import DOMAIN 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 @@ class WizBulbEntity(WizToggleEntity, LightEntity): 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 @@ class WizBulbEntity(WizToggleEntity, LightEntity): 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(): - 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 + if ( + COLOR_MODE_COLOR_TEMP in color_modes + and (color_temp := state.get_colortemp()) 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_color_mode = COLOR_MODE_COLOR_TEMP + self._attr_color_temp = color_temperature_kelvin_to_mired(color_temp) + elif ( + COLOR_MODE_RGBWW in color_modes and (rgbww := state.get_rgbww()) is not None + ): + 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 47a4f343d4f..07b306f1121 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 42be5758130..278989b5b2b 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 5376c7a5f11..09e983c66f9 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 ae3fe7eb5c4..78826760d0b 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 c2f67982b84..57650ede272 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 @@ REAL_BULB_CONFIG = json.loads( "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 28645e53e14..465bc3e3d27 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,34 +11,24 @@ 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 . 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 -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"} - - DHCP_DISCOVERY = dhcp.DhcpServiceInfo( hostname="wiz_abcabc", ip=FAKE_IP, @@ -55,36 +42,6 @@ INTEGRATION_DISCOVERY = { } -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 00000000000..16d7c6a0a5d --- /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 7213d6ac7a0..f43bb3d97c7 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 @@ class LocalSource(MediaSource): @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 @@ class LocalMediaView(HomeAssistantView): 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 8a9005d7a86..f9ee560620c 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.config import async_process_ha_core_config 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 4f1f9f059d8..84caf0ad29a 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 4cea3d9499e..dba041f1028 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.components.climate.const import ( ) 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 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): 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") - self._loc_id = coordinator.data.devices[device_id]["location"] + # 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"] + ] + + 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 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): 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 92867a3dfe0..1d6dfbd3e90 100644 --- a/homeassistant/components/plugwise/sensor.py +++ b/homeassistant/components/plugwise/sensor.py @@ -17,10 +17,10 @@ from homeassistant.const import ( 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 @@ SENSORS: tuple[SensorEntityDescription, ...] = ( 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 @@ SENSORS: tuple[SensorEntityDescription, ...] = ( ), ) -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 @@ class PlugwiseSensorEnity(PlugwiseEntity, SensorEntity): 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 c9053e8468e..ec1f2e5a2b4 100644 --- a/homeassistant/components/plugwise/binary_sensor.py +++ b/homeassistant/components/plugwise/binary_sensor.py @@ -36,6 +36,13 @@ BINARY_SENSORS: tuple[PlugwiseBinarySensorEntityDescription, ...] = ( 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 dba041f1028..f8e1b4f981c 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -21,13 +21,11 @@ from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS 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 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): 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 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): @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 1beeff501b0..ab4bde47d91 100644 --- a/homeassistant/components/plugwise/const.py +++ b/homeassistant/components/plugwise/const.py @@ -14,8 +14,6 @@ FLOW_STRETCH = "stretch (Stretch)" 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 5eb60ebeb6f..5c9df093487 100644 --- a/tests/components/plugwise/test_climate.py +++ b/tests/components/plugwise/test_climate.py @@ -5,6 +5,7 @@ import pytest 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 a4ff2a618bc..77732f4e2cb 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -495,8 +495,6 @@ class SimpliSafe: """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 @@ class SimpliSafe: 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 | 267 +++++++----------- .../components/plugwise/test_binary_sensor.py | 36 ++- tests/components/plugwise/test_climate.py | 121 ++++---- tests/components/plugwise/test_diagnostics.py | 10 +- tests/components/plugwise/test_init.py | 101 ++++--- tests/components/plugwise/test_sensor.py | 60 ++-- tests/components/plugwise/test_switch.py | 66 ++--- 8 files changed, 310 insertions(+), 377 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 379929ce2f1..00000000000 --- 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 fa49967437d..012734315f6 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 typing import Any +from unittest.mock import AsyncMock, MagicMock, patch -from plugwise.exceptions import ( - ConnectionFailedError, - InvalidAuthentication, - PlugwiseException, - XMLDataMissingError, -) 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(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) - - -@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 - - -def _get_device_data(chosen_env, device_id): - """Mock return data for specific devices.""" - return _read_json(chosen_env, "get_device_data/" + device_id) - - -@pytest.fixture(name="mock_smile_adam") -def mock_smile_adam(): +@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" - 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" + with patch( + "homeassistant.components.plugwise.gateway.Smile", autospec=True + ) as smile_mock: + smile = smile_mock.return_value - smile_mock.return_value.notifications = _read_json(chosen_env, "notifications") + 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" - 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 - ) + smile.connect.return_value = True - yield smile_mock.return_value + smile.notifications = _read_json(chosen_env, "notifications") + smile.async_update.return_value = _read_json(chosen_env, "all_data") + + yield smile -@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 + with patch( + "homeassistant.components.plugwise.gateway.Smile", autospec=True + ) as smile_mock: + smile = smile_mock.return_value - 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.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_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.notifications = _read_json(chosen_env, "notifications") + smile.async_update.return_value = _read_json(chosen_env, "all_data") - 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 + yield smile -@pytest.fixture(name="mock_smile_p1") -def mock_smile_p1(): +@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") 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 = "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_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 - ) - - 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 + yield smile -@pytest.fixture(name="mock_stretch") -def mock_stretch(): +@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 + with patch( + "homeassistant.components.plugwise.gateway.Smile", autospec=True + ) as smile_mock: + smile = smile_mock.return_value - 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.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_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.connect.return_value = True + smile.async_update.return_value = _read_json(chosen_env, "all_data") - smile_mock.return_value.async_update.side_effect = AsyncMock( - return_value=_read_json(chosen_env, "all_data") - ) + yield smile - yield smile_mock.return_value + +@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 b2356036eb9..4bcecf83157 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 5c9df093487..6f9a258bb38 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 @@ from homeassistant.components.climate.const import ( 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 state + assert state.attributes["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 "preset_modes" in state.attributes + assert "no_frost" in state.attributes["preset_modes"] + assert "home" in state.attributes["preset_modes"] - assert attrs["current_temperature"] == 20.9 - assert attrs["temperature"] == 21.5 - - 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 state - assert attrs["hvac_modes"] == [HVAC_MODE_HEAT, HVAC_MODE_OFF, HVAC_MODE_AUTO] + assert state.attributes["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 "preset_modes" in state.attributes + assert "no_frost" in state.attributes["preset_modes"] + assert "home" in state.attributes["preset_modes"] - assert attrs["current_temperature"] == 17.2 - assert attrs["temperature"] == 15.0 - - 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 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 attrs["supported_features"] == 17 + 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, mock_smile_anna): +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 fdc4ee87920..673e77f1630 100644 --- a/tests/components/plugwise/test_diagnostics.py +++ b/tests/components/plugwise/test_diagnostics.py @@ -5,18 +5,20 @@ 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.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 d0d810804e2..d63b81bd0d3 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 +from tests.common import MockConfigEntry -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 - - -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={}) - - entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) + await hass.config_entries.async_unload(mock_config_entry.entry_id) await hass.async_block_till_done() - assert entry.state is ConfigEntryState.SETUP_ERROR + + 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 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 47cbea1a8bc..6f5309d3810 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): +async def test_adam_climate_sensor_entities( + hass: HomeAssistant, mock_smile_adam: MagicMock, init_integration: MockConfigEntry +) -> None: """Test creation of climate related sensor entities.""" - entry = await async_init_integration(hass, mock_smile_adam) - assert entry.state is ConfigEntryState.LOADED - 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 4785a222f69..74e19e987d7 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 f8e1b4f981c..a3354007868 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -26,7 +26,12 @@ from .coordinator import PlugwiseDataUpdateCoordinator 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 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): @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 1d6dfbd3e90..4ee75e21a45 100644 --- a/homeassistant/components/plugwise/sensor.py +++ b/homeassistant/components/plugwise/sensor.py @@ -249,6 +249,13 @@ SENSORS: tuple[SensorEntityDescription, ...] = ( 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 b797491b7a9..6cb2eb41d9c 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -21,7 +21,6 @@ import certifi 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 @@ from homeassistant.const import ( CONF_VALUE_TEMPLATE, EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STOP, + SERVICE_RELOAD, Platform, ) from homeassistant.core import ( @@ -76,6 +76,7 @@ from .const import ( 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 async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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 5d7c67d621b..cca4e58658c 100644 --- a/homeassistant/components/mqtt/alarm_control_panel.py +++ b/homeassistant/components/mqtt/alarm_control_panel.py @@ -35,10 +35,9 @@ from homeassistant.const import ( 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 @@ from .const import ( 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 17daaaf08ee..166b7cda34c 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -28,20 +28,20 @@ import homeassistant.helpers.config_validation as cv 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 c4b4b19b120..22ee7b6d5ae 100644 --- a/homeassistant/components/mqtt/button.py +++ b/homeassistant/components/mqtt/button.py @@ -12,10 +12,9 @@ from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME 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 @@ from .const import ( 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 1c060f7f32a..0e387023a39 100644 --- a/homeassistant/components/mqtt/camera.py +++ b/homeassistant/components/mqtt/camera.py @@ -12,14 +12,18 @@ from homeassistant.const import CONF_NAME 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 2b4e03f8663..043a291f159 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -53,20 +53,23 @@ from homeassistant.const import ( 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 84322ddc1ee..a26e62b6227 100644 --- a/homeassistant/components/mqtt/config_flow.py +++ b/homeassistant/components/mqtt/config_flow.py @@ -84,17 +84,6 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 46396820545..f04348ee002 100644 --- a/homeassistant/components/mqtt/const.py +++ b/homeassistant/components/mqtt/const.py @@ -23,6 +23,7 @@ CONF_TOPIC = "topic" 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 4dfa9e20798..282e57fea9e 100644 --- a/homeassistant/components/mqtt/cover.py +++ b/homeassistant/components/mqtt/cover.py @@ -37,10 +37,9 @@ from homeassistant.const import ( 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 @@ from .const import ( 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 114c8f5729f..bedc3467c3b 100644 --- a/homeassistant/components/mqtt/fan.py +++ b/homeassistant/components/mqtt/fan.py @@ -32,7 +32,6 @@ from homeassistant.const import ( 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 @@ from homeassistant.util.percentage import ( 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 @@ from .const import ( 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 0ab99cf0914..99674051521 100644 --- a/homeassistant/components/mqtt/humidifier.py +++ b/homeassistant/components/mqtt/humidifier.py @@ -27,10 +27,9 @@ from homeassistant.const import ( 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 @@ from .const import ( 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 09dfb8417cc..d78cd5e7baa 100644 --- a/homeassistant/components/mqtt/light/__init__.py +++ b/homeassistant/components/mqtt/light/__init__.py @@ -8,11 +8,9 @@ import voluptuous as vol 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 788c6be1fef..1dd73175b3b 100644 --- a/homeassistant/components/mqtt/lock.py +++ b/homeassistant/components/mqtt/lock.py @@ -12,10 +12,9 @@ from homeassistant.const import CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE 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 @@ from .const import ( 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 fe989acb98e..421ad3203b3 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -5,9 +5,11 @@ from abc import abstractmethod 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 @@ from homeassistant.const import ( 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 @@ from homeassistant.helpers.entity import ( 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 @@ from .const import ( CONF_ENCODING, CONF_QOS, CONF_TOPIC, + DATA_MQTT_RELOAD_NEEDED, DEFAULT_ENCODING, DEFAULT_PAYLOAD_AVAILABLE, DEFAULT_PAYLOAD_NOT_AVAILABLE, @@ -210,6 +222,20 @@ MQTT_ENTITY_COMMON_SCHEMA = MQTT_AVAILABILITY_SCHEMA.extend( ) +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_setup_entry_helper(hass, domain, async_setup, schema): ) +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 6f9c4c38ead..e13ae4ded84 100644 --- a/homeassistant/components/mqtt/number.py +++ b/homeassistant/components/mqtt/number.py @@ -23,11 +23,10 @@ from homeassistant.const import ( 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 @@ from .const import ( 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 b12eb2d336a..c44ea1dca53 100644 --- a/homeassistant/components/mqtt/scene.py +++ b/homeassistant/components/mqtt/scene.py @@ -12,18 +12,17 @@ from homeassistant.const import CONF_ICON, CONF_NAME, CONF_PAYLOAD_ON, CONF_UNIQ 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 58e6e7e4d64..f873a32a5de 100644 --- a/homeassistant/components/mqtt/select.py +++ b/homeassistant/components/mqtt/select.py @@ -13,11 +13,10 @@ from homeassistant.const import CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE 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 @@ from .const import ( 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 137627047bc..8dd53901755 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -30,20 +30,20 @@ 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 -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 e43edb12873..8619bc799a4 100644 --- a/homeassistant/components/mqtt/siren.py +++ b/homeassistant/components/mqtt/siren.py @@ -36,10 +36,9 @@ from homeassistant.const import ( 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 @@ from .const import ( 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 f0ec18cc379..1a471ea2ad0 100644 --- a/homeassistant/components/mqtt/switch.py +++ b/homeassistant/components/mqtt/switch.py @@ -20,11 +20,10 @@ from homeassistant.const import ( 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 @@ from .const import ( 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 6ff03a437e5..f64a67820d4 100644 --- a/homeassistant/components/mqtt/vacuum/__init__.py +++ b/homeassistant/components/mqtt/vacuum/__init__.py @@ -3,11 +3,9 @@ import functools 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 @@ PLATFORM_SCHEMA = vol.All( 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 091048513c7..a278cde768b 100644 --- a/tests/components/mqtt/test_alarm_control_panel.py +++ b/tests/components/mqtt/test_alarm_control_panel.py @@ -53,6 +53,7 @@ from .test_common import ( 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 aa726f4fff2..5fa71d73632 100644 --- a/tests/components/mqtt/test_binary_sensor.py +++ b/tests/components/mqtt/test_binary_sensor.py @@ -38,6 +38,7 @@ from .test_common import ( 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 e4997085ce2..83ef7a42705 100644 --- a/tests/components/mqtt/test_button.py +++ b/tests/components/mqtt/test_button.py @@ -26,6 +26,7 @@ from .test_common import ( 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 936e4ef4664..07fd7dc2c14 100644 --- a/tests/components/mqtt/test_camera.py +++ b/tests/components/mqtt/test_camera.py @@ -27,6 +27,7 @@ from .test_common import ( 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 16c765dc51a..624823e0ebb 100644 --- a/tests/components/mqtt/test_climate.py +++ b/tests/components/mqtt/test_climate.py @@ -58,6 +58,7 @@ from .test_common import ( 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 758cdd801ae..8cf7353d196 100644 --- a/tests/components/mqtt/test_common.py +++ b/tests/components/mqtt/test_common.py @@ -21,7 +21,7 @@ from homeassistant.helpers import device_registry as dr, entity_registry as er 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 befdc139eeb..83dafd6b436 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 59e03dadfe8..aad6fa5d9ca 100644 --- a/tests/components/mqtt/test_cover.py +++ b/tests/components/mqtt/test_cover.py @@ -64,6 +64,7 @@ from .test_common import ( 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 9ce5e54262e..5418727ec0e 100644 --- a/tests/components/mqtt/test_fan.py +++ b/tests/components/mqtt/test_fan.py @@ -51,6 +51,7 @@ from .test_common import ( 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 f8685898ed5..4aa5ff2350b 100644 --- a/tests/components/mqtt/test_humidifier.py +++ b/tests/components/mqtt/test_humidifier.py @@ -52,6 +52,7 @@ from .test_common import ( 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 3d249fa2144..92884dcef93 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -1134,6 +1134,8 @@ async def test_setup_without_tls_config_uses_tlsv1_under_python36(hass): 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 test_custom_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, }, } ], @@ -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 test_delayed_birth_message(hass, mqtt_client_mock, mqtt_config): 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 d6e1524fc40..1667053f65b 100644 --- a/tests/components/mqtt/test_legacy_vacuum.py +++ b/tests/components/mqtt/test_legacy_vacuum.py @@ -51,6 +51,7 @@ from .test_common import ( 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 0eb77990d32..782ee40f0c8 100644 --- a/tests/components/mqtt/test_light.py +++ b/tests/components/mqtt/test_light.py @@ -202,6 +202,7 @@ from .test_common import ( 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 8f2dce599ac..93bb9b0f573 100644 --- a/tests/components/mqtt/test_light_json.py +++ b/tests/components/mqtt/test_light_json.py @@ -127,6 +127,7 @@ from .test_common import ( 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 c68ed8e7f35..4461cf14ef4 100644 --- a/tests/components/mqtt/test_light_template.py +++ b/tests/components/mqtt/test_light_template.py @@ -65,6 +65,7 @@ from .test_common import ( 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 35849c4f9fc..86e21a261a3 100644 --- a/tests/components/mqtt/test_lock.py +++ b/tests/components/mqtt/test_lock.py @@ -40,6 +40,7 @@ from .test_common import ( 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 70bc1b40e75..73c1da357fa 100644 --- a/tests/components/mqtt/test_number.py +++ b/tests/components/mqtt/test_number.py @@ -46,6 +46,7 @@ from .test_common import ( 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 97f13ba90c0..1ccacc1c5ee 100644 --- a/tests/components/mqtt/test_scene.py +++ b/tests/components/mqtt/test_scene.py @@ -19,6 +19,7 @@ from .test_common import ( 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 f6f005bbfb5..069c0dfe4c9 100644 --- a/tests/components/mqtt/test_select.py +++ b/tests/components/mqtt/test_select.py @@ -37,6 +37,7 @@ from .test_common import ( 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 b556bcf7537..8a1be6b11e2 100644 --- a/tests/components/mqtt/test_sensor.py +++ b/tests/components/mqtt/test_sensor.py @@ -45,6 +45,7 @@ from .test_common import ( 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 7f174582a46..f39154badc9 100644 --- a/tests/components/mqtt/test_siren.py +++ b/tests/components/mqtt/test_siren.py @@ -38,6 +38,7 @@ from .test_common import ( 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 7b8928ccb32..8691aa73323 100644 --- a/tests/components/mqtt/test_state_vacuum.py +++ b/tests/components/mqtt/test_state_vacuum.py @@ -54,6 +54,7 @@ from .test_common import ( 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 79ee56998e8..a458ac03baa 100644 --- a/tests/components/mqtt/test_switch.py +++ b/tests/components/mqtt/test_switch.py @@ -36,6 +36,7 @@ from .test_common import ( 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 9f0958e6ace..564480a0e91 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -38,6 +38,7 @@ pytest.register_assert_rewrite("tests.common") 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 ed530704550..16ca58448e2 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -79,6 +79,7 @@ SUBSCRIPTION_SERVICES = [ "renderingControl", "zoneGroupTopology", ] +SUPPORTED_VANISH_REASONS = ("sleeping", "upgrade") UNAVAILABLE_VALUES = {"", "NOT_IMPLEMENTED", None} UNUSED_DEVICE_KEYS = ["SPID", "TargetRoomName"] @@ -553,25 +554,29 @@ class SonosSpeaker: 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 @@ class SonosSpeaker: 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 @@ class SonosSpeaker: 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 f43bb3d97c7..66e6bdd8379 100644 --- a/homeassistant/components/media_source/local_source.py +++ b/homeassistant/components/media_source/local_source.py @@ -4,10 +4,10 @@ from __future__ import annotations 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 2d5dbc5195a..2f6ea3c20cb 100644 --- a/homeassistant/components/sonos/const.py +++ b/homeassistant/components/sonos/const.py @@ -44,6 +44,7 @@ SONOS_ALBUM_ARTIST = "album_artists" 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 @@ SONOS_TO_MEDIA_CLASSES = { "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 @@ SONOS_TYPES_MAPPING = { "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 2e3bf9d1fcb..2d971469928 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 a2eb70e1c42..b955f1a0249 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 15b46a14ae0..a29990b6d44 100644 --- a/homeassistant/components/wiz/__init__.py +++ b/homeassistant/components/wiz/__init__.py @@ -83,6 +83,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) 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 3fe3b9071b0..aa564a14f33 100644 --- a/homeassistant/components/wiz/config_flow.py +++ b/homeassistant/components/wiz/config_flow.py @@ -12,7 +12,7 @@ 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 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 465bc3e3d27..dc4bd4de329 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 5599965a2a6..24266e2d8bb 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 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, - 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 @@ __all__ = [ ] +@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 0ffdfd6ace6..ae7c24e80d2 100644 --- a/homeassistant/components/spotify/browse_media.py +++ b/homeassistant/components/spotify/browse_media.py @@ -27,15 +27,7 @@ from homeassistant.components.media_player.const import ( 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 6e54ed21ec1..4c86234045b 100644 --- a/homeassistant/components/spotify/const.py +++ b/homeassistant/components/spotify/const.py @@ -9,10 +9,6 @@ from homeassistant.components.media_player.const import ( 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 58f581fdf82..cdcc0132f93 100644 --- a/homeassistant/components/spotify/media_player.py +++ b/homeassistant/components/spotify/media_player.py @@ -5,7 +5,6 @@ from asyncio import run_coroutine_threadsafe 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 @@ from homeassistant.components.media_player.const import ( 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.data = data - self._currently_playing: dict | None = {} - self._devices: list[dict] | None = [] - self._playlist: dict | None = None - - 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] + if self.data.current_user["product"] == "premium": + self._attr_supported_features = SUPPORT_SPOTIFY - @property - def _session(self) -> OAuth2Session: - """Return spotify session.""" - return self._spotify_data[DATA_SPOTIFY_SESSION] - - @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 @@ class SpotifyMediaPlayer(MediaPlayerEntity): 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 @@ class SpotifyMediaPlayer(MediaPlayerEntity): ): 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 @@ class SpotifyMediaPlayer(MediaPlayerEntity): 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 @@ class SpotifyMediaPlayer(MediaPlayerEntity): @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 @@ class SpotifyMediaPlayer(MediaPlayerEntity): 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 @@ class SpotifyMediaPlayer(MediaPlayerEntity): 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 5cccc40ac5f..f011cb2d754 100644 --- a/homeassistant/components/sonos/sensor.py +++ b/homeassistant/components/sonos/sensor.py @@ -11,7 +11,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect 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 @@ class SonosAudioInputFormatSensorEntity(SonosPollingEntity, SensorEntity): 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 14ad17bec8b..d7791c6ce72 100644 --- a/tests/components/sonos/conftest.py +++ b/tests/components/sonos/conftest.py @@ -3,6 +3,7 @@ from copy import copy 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 8fb75789149..bda4e08cd25 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 1a3c6b52a0d..be24be632fc 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 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity): ) -> 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 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity): 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 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity): 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 6bf60f7f5b0..09fe16215b6 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 @@ class PhilipsTVRemote(CoordinatorEntity, RemoteEntity): 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 d3c5094d4b1..171415f1c79 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 00000000000..889b8e47e3f --- /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 d0473c4f5c3..ef60deea956 100644 --- a/homeassistant/components/wiz/light.py +++ b/homeassistant/components/wiz/light.py @@ -108,9 +108,8 @@ class WizBulbEntity(WizToggleEntity, LightEntity): 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 8f5fbb244ff..e5118e6f9ee 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 1eb34e21000..302c0df7f05 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 e99d67902e0..ef665c89805 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 d86e777877e..8e2990982d6 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 665ac4b4d92..05560930fc3 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 39452b58094..ced77ff0d04 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 bbf0437ba06..7cb9a5f101a 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 b41de6b5657..e1c9567dc0f 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 00000000000..760a3a91365 --- /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 00000000000..dd801a69f33 --- /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 36bc21ec26d..7b2e7958308 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 00000000000..ac292c10b64 --- /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 2b179dda182..b576192718f 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 b7c45c6bad1..c6da81663ca 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 270a6275791..db163efeac7 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 05f5985a8df..a69191d05c6 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 00000000000..9da1f007992 --- /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 66d3edbc978..6ddc51565fb 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 fe72a0df172..4628b7bea8b 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 763d028803a..08e53ad8d49 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 9c9414caf9b..45a6f6fe594 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 b822522af10..a8d4b08ed83 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 00000000000..120b7ef6d4a --- /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 00000000000..b35bb0c25cc --- /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 00000000000..d15111e97b0 --- /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 00000000000..d2bae9be52c --- /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 00000000000..8f3df1c4b71 --- /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 00000000000..7c97f50a85f --- /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 00000000000..72abc5f1e5f --- /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 00000000000..7de2c8da4d8 --- /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 00000000000..32fae944a71 --- /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 00000000000..b5f02d5b3d5 --- /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 00000000000..e843c048d89 --- /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 50cac3172ab..89a5ce04d97 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 4bb5f5cfd15..411501db49d 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 52020ae3824..73735f3e91a 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 201ecbf3779..ed691aa388f 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 35530bd2c86..cecdab86436 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 3bac269eaf9..370796c18f3 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 6f45fb144f5..01cf58c6ed4 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 be139286113..1235f36d26a 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 ec535090348..6bf27915f97 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 00000000000..a0d7a14dcdb --- /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 3c32100078d..b3e9684424b 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 a01a11bf5df..caebd296512 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 71b49b9ad2f..57b5dc30d48 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 795e4c5d43e..7041d3f27c6 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 2992fcb2f4b..daf17a266f2 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 cfd4d9813c8..f602994d8b2 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 00000000000..d5d7602e084 --- /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 82b4196eb75..d6628f9747c 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 d1abe4aa1be..ad8951c92c5 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 f481be62e73..278dda51208 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 00000000000..5d7bc400600 --- /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 2614230087b..21a6adca854 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 00000000000..bf6563a3f48 --- /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 16f679e9c7d..bef8eae0d32 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 50ad7f4b9ca..b78e4a327a1 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 00000000000..8a9a6928e93 --- /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 d66c56d208f..6e7ac1257fa 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 09e983c66f9..9ce6f6294d9 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 78826760d0b..c5ff306c079 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 9b340096fde..8a7c0f93592 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 71a8b6c83aa..1eb923b91a8 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 9ce6f6294d9..c7906515c38 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 c5ff306c079..25ca7e4c50b 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 9a3ae9f8d6c..34b65c5b791 100644 --- a/homeassistant/components/ihc/__init__.py +++ b/homeassistant/components/ihc/__init__.py @@ -10,13 +10,18 @@ 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_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 f782cd590c0..ae271108848 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 7a981fc1b63..48035d27a4d 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 2f3651c7bb7..c86e77870c8 100644 --- a/homeassistant/components/ihc/const.py +++ b/homeassistant/components/ihc/const.py @@ -25,6 +25,7 @@ CONF_XPATH = "xpath" 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 e351d2f38ea..31887c51397 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 @@ class IHCDevice(Entity): 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 @@ class IHCDevice(Entity): """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 b6269865072..b86f9fb3c8a 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.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_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 a68230c9900..297997281c6 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 d9dcab431d0..d3c38687caa 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.entity_platform import AddEntitiesCallback 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 6136c8ccd46..3d7008ee38b 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 @@ from .const import ( 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 .const import ( ) 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 @@ PULSE_SCHEMA = vol.Schema( ) -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 fb1e2f1642d..e33d3b6bb5e 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 6cb2eb41d9c..7c3bc63779b 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -21,6 +21,7 @@ import certifi 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 f5cef1e7484..a0017c36684 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -1393,12 +1393,24 @@ class ConfigFlow(data_entry_flow.FlowHandler): 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 @@ class ConfigFlow(data_entry_flow.FlowHandler): """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 83dafd6b436..f16a0e5e83a 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 20fe7d086e0..b7b688a0e73 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 183627fdfa6..aa276af492c 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 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: _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 async_setup(hass: HomeAssistant, config: ConfigType) -> bool: 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 async_setup(hass: HomeAssistant, config: ConfigType) -> bool: 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 a7e6a29bfe8..2154cd68772 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 c7906515c38..ce3a8f71828 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 611cf57e56b..59902335d47 100644 --- a/homeassistant/components/microsoft/tts.py +++ b/homeassistant/components/microsoft/tts.py @@ -58,6 +58,7 @@ SUPPORTED_LANGUAGES = [ "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 ec1f2e5a2b4..129f4faef92 100644 --- a/homeassistant/components/plugwise/binary_sensor.py +++ b/homeassistant/components/plugwise/binary_sensor.py @@ -43,6 +43,20 @@ BINARY_SENSORS: tuple[PlugwiseBinarySensorEntityDescription, ...] = ( 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 @@ class PlugwiseBinarySensorEntity(PlugwiseEntity, BinarySensorEntity): @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 4bcecf83157..aacb9e469bb 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 b38ab3ef5d3..c9a72aa2d8e 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 ce3a8f71828..916818db1c0 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 25ca7e4c50b..7ad4cfc748a 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 b670c734b47..e9038d1f658 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 5f49889b6c6..714ac037e2a 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 8fa18ce3142..320ce092427 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -326,7 +326,10 @@ class LgWebOSMediaPlayerEntity(RestoreEntity, MediaPlayerEntity): 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 450d3d90377..c9cc4a78aee 100644 --- a/tests/components/webostv/test_media_player.py +++ b/tests/components/webostv/test_media_player.py @@ -59,6 +59,7 @@ from homeassistant.const import ( 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 homeassistant.util import dt 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 136fde657e5..47efe9bf18e 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 916818db1c0..b07ac4156ad 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 7ad4cfc748a..a61afbd574d 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 a29990b6d44..abdbbf1191a 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 07b306f1121..1296cf50d1b 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 b07ac4156ad..292f3910904 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 a61afbd574d..6869cde00ad 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 cdcc0132f93..fb431ab4824 100644 --- a/homeassistant/components/spotify/media_player.py +++ b/homeassistant/components/spotify/media_player.py @@ -378,10 +378,13 @@ class SpotifyMediaPlayer(MediaPlayerEntity): 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 24266e2d8bb..fb66622d893 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 @@ from homeassistant.helpers.config_entry_oauth2_flow import ( 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 4c86234045b..ad73262921b 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 @@ from homeassistant.components.media_player.const import ( 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 fb431ab4824..f6b229e99fd 100644 --- a/homeassistant/components/spotify/media_player.py +++ b/homeassistant/components/spotify/media_player.py @@ -33,7 +33,7 @@ from homeassistant.components.media_player.const import ( ) 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 @@ class SpotifyMediaPlayer(MediaPlayerEntity): SPOTIFY_SCOPES ) self._currently_playing: dict | None = {} - self._devices: list[dict] | None = [] self._playlist: dict | None = None @property @@ -258,9 +257,7 @@ class SpotifyMediaPlayer(MediaPlayerEntity): @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 @@ class SpotifyMediaPlayer(MediaPlayerEntity): 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 @@ class SpotifyMediaPlayer(MediaPlayerEntity): 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 @@ class SpotifyMediaPlayer(MediaPlayerEntity): 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 8a7c0f93592..031f513843f 100644 --- a/homeassistant/components/august/__init__.py +++ b/homeassistant/components/august/__init__.py @@ -107,11 +107,10 @@ class AugustData(AugustSubscriberMixin): 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 3d28086db43..8f4a8b0aca4 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 a1933ff9315..53eabe225f6 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 cadef8c8a0d..7b307f20274 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 @@ class PVOutputDataUpdateCoordinator(DataUpdateCoordinator[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 042c6b9aa99..021fffe0e01 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 292f3910904..7d05f5eca7b 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 6869cde00ad..983b50d9e7f 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 0c060a75a9d..8cd776beea3 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 faaff3d4214..b583e0807e0 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 686a7c13e58..8d21b365ad6 100644 --- a/homeassistant/components/cpuspeed/sensor.py +++ b/homeassistant/components/cpuspeed/sensor.py @@ -81,8 +81,8 @@ class CPUSpeedSensor(SensorEntity): 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 134d19b31ea..ebf9f0111bd 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 efdccdb1a43..a206f562c17 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 eeca98167d0..e4f8f715bc8 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -14,7 +14,6 @@ from aiohomekit.model.characteristics import ( ) 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 homeassistant.helpers.typing import ConfigType 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 54d265a5f4a..9053c79b939 100644 --- a/homeassistant/components/homekit_controller/config_flow.py +++ b/homeassistant/components/homekit_controller/config_flow.py @@ -20,6 +20,7 @@ from homeassistant.helpers.device_registry import ( ) from .const import DOMAIN, KNOWN_DEVICES +from .utils import async_get_controller HOMEKIT_DIR = ".homekit" HOMEKIT_BRIDGE_DOMAIN = "homekit" @@ -104,10 +105,7 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 8cc6ce2b575..ce7ae876e03 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 00000000000..6831c3cee4a --- /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 d4db04e8e82..bbab7d20f80 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 7d05f5eca7b..6d373252f81 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 983b50d9e7f..c6ff0311cca 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 4fabe504d4b..7fae69ee01b 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 46b8a5de3e7..81688f88a4b 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 d1b133468d5..03694e7186a 100644 --- a/tests/components/homekit_controller/test_init.py +++ b/tests/components/homekit_controller/test_init.py @@ -4,7 +4,6 @@ from unittest.mock import patch 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 19e737f51ca..6d994e472c8 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 6d373252f81..b2a15ae0064 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 c6ff0311cca..03a713478e6 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 b6bfd2122ba..79506c0bda2 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -338,7 +338,6 @@ class Stream: ) 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 546bb055ca8..0408f547f25 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 a4b7064e564..5fcead53da2 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 a2193307b18..5b8e1545406 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -604,6 +604,10 @@ DHCP = [ "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 8282795a8e9..58e2d3a1b52 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 5b8e1545406..a3d29f3107d 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -51,6 +51,11 @@ DHCP = [ "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 bb9d9ad7569..71fd3e1c241 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 a3d29f3107d..2fbefe9bbca 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -312,6 +312,10 @@ DHCP = [ "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 63fc3172253..2849b249762 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -8,6 +8,8 @@ import logging import async_timeout from miio import ( AirFresh, + AirFreshA1, + AirFreshT2017, AirHumidifier, AirHumidifierMiot, AirHumidifierMjjsq, @@ -48,6 +50,8 @@ from .const import ( 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 b361e8ba1b3..cc607b3f419 100644 --- a/homeassistant/components/xiaomi_miio/const.py +++ b/homeassistant/components/xiaomi_miio/const.py @@ -75,7 +75,9 @@ MODEL_AIRHUMIDIFIER_JSQ = "deerma.humidifier.jsq" 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 @@ MODELS_PURIFIER_MIIO = [ 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 @@ FEATURE_FLAGS_AIRHUMIDIFIER_CA4 = ( | 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 @@ FEATURE_FLAGS_AIRFRESH = ( | 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 a12a8a6063b..1337aa05895 100644 --- a/homeassistant/components/xiaomi_miio/fan.py +++ b/homeassistant/components/xiaomi_miio/fan.py @@ -5,6 +5,7 @@ import logging 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 @@ from .const import ( 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 @@ from .const import ( 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_SLEEP_MODE = "sleep_mode" 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 @@ PRESET_MODES_AIRPURIFIER_V3 = [ "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 @@ class XiaomiAirFresh(XiaomiGenericAirPurifier): ) +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 8939200d107..4ffc773d1c1 100644 --- a/homeassistant/components/xiaomi_miio/number.py +++ b/homeassistant/components/xiaomi_miio/number.py @@ -18,6 +18,8 @@ from .const import ( 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 @@ from .const import ( 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 @@ NUMBER_TYPES = { } 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 d6d1c8500ed..6095bfe5647 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -52,6 +52,8 @@ from .const import ( DOMAIN, KEY_COORDINATOR, KEY_DEVICE, + MODEL_AIRFRESH_A1, + MODEL_AIRFRESH_T2017, MODEL_AIRFRESH_VA2, MODEL_AIRHUMIDIFIER_CA1, MODEL_AIRHUMIDIFIER_CB1, @@ -375,6 +377,14 @@ AIRFRESH_SENSORS = ( 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 @@ FAN_V2_V3_SENSORS = ( 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 bd6482e891b..ce05a891c0a 100644 --- a/homeassistant/components/xiaomi_miio/switch.py +++ b/homeassistant/components/xiaomi_miio/switch.py @@ -35,6 +35,8 @@ from .const import ( 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 @@ from .const import ( 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 @@ SERVICE_TO_METHOD = { } 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 9053c79b939..3784e2830d7 100644 --- a/homeassistant/components/homekit_controller/config_flow.py +++ b/homeassistant/components/homekit_controller/config_flow.py @@ -162,6 +162,7 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 1dc70cde610..78ad9b25cd7 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 b4811e57739..ca617026d94 100644 --- a/tests/components/apple_tv/test_config_flow.py +++ b/tests/components/apple_tv/test_config_flow.py @@ -22,6 +22,7 @@ from tests.common import MockConfigEntry 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 @@ DMAP_SERVICE = zeroconf.ZeroconfServiceInfo( 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 bc03cb99f07..112fa57fa64 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 d43845e01ad..cca62babbb5 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 b36637897d8..5d3b357b9f7 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 065035bedbe..e7c5e6d7d4d 100644 --- a/tests/components/bosch_shc/test_config_flow.py +++ b/tests/components/bosch_shc/test_config_flow.py @@ -22,6 +22,7 @@ MOCK_SETTINGS = { } 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 3bc7738bb8d..6dbaebdfa7b 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 5421f6e1d52..3a6a56fe097 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 96686e204eb..96090195d20 100644 --- a/tests/components/devolo_home_control/const.py +++ b/tests/components/devolo_home_control/const.py @@ -4,6 +4,7 @@ from homeassistant.components import zeroconf 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 = zeroconf.ZeroconfServiceInfo( 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_DEVOLO_DEVICE = zeroconf.ZeroconfServiceInfo( 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 681c2673dff..0e48833a78b 100644 --- a/tests/components/devolo_home_network/const.py +++ b/tests/components/devolo_home_network/const.py @@ -18,6 +18,7 @@ CONNECTED_STATIONS = { DISCOVERY_INFO = zeroconf.ZeroconfServiceInfo( host=IP, + addresses=[IP], port=14791, hostname="test.local.", type="_dvl-deviceapi._tcp.local.", @@ -38,6 +39,7 @@ DISCOVERY_INFO = zeroconf.ZeroconfServiceInfo( 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 ccb05b327ac..10cc4a4fb77 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 dffd59cedcc..fdc0ad834d1 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 d8b23ac9864..76179c02e22 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 25103c4fe2a..f7da5d66bd5 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 abd2f1ab3a2..fd4d82b177c 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 3bffd213b72..cee1c28cebd 100644 --- a/tests/components/freebox/test_config_flow.py +++ b/tests/components/freebox/test_config_flow.py @@ -20,6 +20,7 @@ from tests.common import MockConfigEntry 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 a20687c5f3d..713295c0efd 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 fc3157289e9..a56aa6355e8 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 7fae69ee01b..8f59fae8639 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 c9354297a43..8a1f77d0e4c 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 061b7b634d4..d0dc7d04509 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 0aa032ddb0d..3c7a07ef5d6 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 7f5d4a569ed..114a747590e 100644 --- a/tests/components/hunterdouglas_powerview/test_config_flow.py +++ b/tests/components/hunterdouglas_powerview/test_config_flow.py @@ -13,6 +13,7 @@ from tests.common import MockConfigEntry, load_fixture 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 @@ HOMEKIT_DISCOVERY_INFO = zeroconf.ZeroconfServiceInfo( 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 0e88fb21baf..26e2c9b338e 100644 --- a/tests/components/ipp/__init__.py +++ b/tests/components/ipp/__init__.py @@ -38,6 +38,7 @@ MOCK_ZEROCONF_IPP_SERVICE_INFO = zeroconf.ZeroconfServiceInfo( 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 @@ MOCK_ZEROCONF_IPPS_SERVICE_INFO = zeroconf.ZeroconfServiceInfo( 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 c3aaca16d5a..5b8b07583c5 100644 --- a/tests/components/kodi/util.py +++ b/tests/components/kodi/util.py @@ -17,6 +17,7 @@ TEST_WS_PORT = {"ws_port": 9090} 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 = zeroconf.ZeroconfServiceInfo( 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 911e984a57e..11426f20e57 100644 --- a/tests/components/lookin/__init__.py +++ b/tests/components/lookin/__init__.py @@ -19,6 +19,7 @@ ZC_NAME = f"LOOKin_{DEVICE_ID}" 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 2b947c36982..47956e27002 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 b1b2eb618af..931d2918fe2 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 015c645a3e7..9479e29cdea 100644 --- a/tests/components/nam/test_config_flow.py +++ b/tests/components/nam/test_config_flow.py @@ -14,6 +14,7 @@ from tests.common import MockConfigEntry 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 4e4a48e9bfe..305f88a2e90 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 b97f4c8b4ec..30fb5fd3d47 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 733d5807c5f..2261fec3a86 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 422b47668aa..7769e082016 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 e80add482b0..db86a4abc5c 100644 --- a/tests/components/overkiz/test_config_flow.py +++ b/tests/components/overkiz/test_config_flow.py @@ -33,6 +33,7 @@ MOCK_GATEWAY2_RESPONSE = [Mock(id=TEST_GATEWAY_ID2)] 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 3e9ce985a5c..9fdd0323518 100644 --- a/tests/components/plugwise/test_config_flow.py +++ b/tests/components/plugwise/test_config_flow.py @@ -38,6 +38,7 @@ TEST_USERNAME2 = "stretch" 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_DISCOVERY = ZeroconfServiceInfo( 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 cf9e811ed5a..00445f23c01 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 4514bbfb9d8..5a0e1fc08cc 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 c0e044a3589..2ae0b308f9a 100644 --- a/tests/components/roku/__init__.py +++ b/tests/components/roku/__init__.py @@ -24,6 +24,7 @@ HOMEKIT_HOST = "192.168.1.161" 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 c2a258b2afa..199c7d87eed 100644 --- a/tests/components/samsungtv/test_config_flow.py +++ b/tests/components/samsungtv/test_config_flow.py @@ -101,6 +101,7 @@ MOCK_DHCP_DATA = dhcp.DhcpServiceInfo( 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 12690a35faa..02e86ef03f8 100644 --- a/tests/components/shelly/test_config_flow.py +++ b/tests/components/shelly/test_config_flow.py @@ -19,6 +19,7 @@ MOCK_SETTINGS = { } 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 f6f988a3caa..16d330a21a8 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 d7791c6ce72..bc49c12ed81 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 aa2ce6cc0be..f0e6c81a411 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 a1a77da1d10..e2f1878c04d 100644 --- a/tests/components/spotify/test_config_flow.py +++ b/tests/components/spotify/test_config_flow.py @@ -15,6 +15,7 @@ from tests.common import MockConfigEntry 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 ecc36641e6c..94d116bbd36 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_INPUT = { 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 = zeroconf.ZeroconfServiceInfo( 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 4c234fee248..8e30ff37de6 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 12726bc553a..90fce929f58 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 b4a3fc04766..e39864a6157 100644 --- a/tests/components/vizio/const.py +++ b/tests/components/vizio/const.py @@ -199,6 +199,7 @@ ZEROCONF_PORT = HOST.split(":")[1] 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 3eb254784d1..8a47155815c 100644 --- a/tests/components/volumio/test_config_flow.py +++ b/tests/components/volumio/test_config_flow.py @@ -19,6 +19,7 @@ TEST_CONNECTION = { 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 27023708400..c23f35534b8 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 554e0460443..c27f273f26b 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 3be52e83237..09f0b4c0fb6 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 81972ca4352..d0112c5a544 100644 --- a/tests/components/yeelight/__init__.py +++ b/tests/components/yeelight/__init__.py @@ -42,6 +42,7 @@ ID_DECIMAL = f"{int(ID, 16):08d}" 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 aa5e7f98a45..dd6c8fe1cbd 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 24b6ec97ec6..dc007d5c2c5 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 fe03f759304..0c51ecffe9b 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 e583aad904b..697dc0cf4c4 100644 --- a/homeassistant/components/amcrest/binary_sensor.py +++ b/homeassistant/components/amcrest/binary_sensor.py @@ -65,6 +65,10 @@ _MOTION_DETECTED_EVENT_CODE = "VideoMotion" _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 @@ BINARY_SENSORS: tuple[AmcrestSensorEntityDescription, ...] = ( 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 00000000000..d7f2a5ba4ae --- /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 ce1ede86538..26889427555 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 ae3fcdf90e3..ac6f463467f 100644 --- a/homeassistant/components/august/const.py +++ b/homeassistant/components/august/const.py @@ -46,6 +46,7 @@ ACTIVITY_UPDATE_INTERVAL = timedelta(seconds=10) 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 a0fe44838c2..209747da0be 100644 --- a/homeassistant/components/august/entity.py +++ b/homeassistant/components/august/entity.py @@ -36,6 +36,11 @@ class AugustEntityMixin(Entity): 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 41abc1c6aa2..e30f8301a8f 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 2d572b886f3..e419488becc 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 @@ async def _create_august_with_devices( # noqa: C901 # 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 00000000000..2d3e6caf884 --- /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 9c7c9f51e24..36a73c4fc06 100644 --- a/tests/components/zwave_me/test_config_flow.py +++ b/tests/components/zwave_me/test_config_flow.py @@ -18,6 +18,7 @@ MOCK_ZEROCONF_DATA = zeroconf.ZeroconfServiceInfo( 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 171415f1c79..0ada739763f 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 62082bb9ab4..747dcaa58be 100644 --- a/homeassistant/components/intellifire/binary_sensor.py +++ b/homeassistant/components/intellifire/binary_sensor.py @@ -13,10 +13,10 @@ from homeassistant.components.binary_sensor import ( 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 00000000000..eeb5e7b51bd --- /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 991e4e69e8c..7c52581498b 100644 --- a/homeassistant/components/intellifire/sensor.py +++ b/homeassistant/components/intellifire/sensor.py @@ -17,47 +17,11 @@ from homeassistant.config_entries import ConfigEntry 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 @@ INTELLIFIRE_SENSORS: tuple[IntellifireSensorEntityDescription, ...] = ( 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 1567f22be44..62daa639f50 100644 --- a/homeassistant/components/yale_smart_alarm/config_flow.py +++ b/homeassistant/components/yale_smart_alarm/config_flow.py @@ -27,7 +27,6 @@ DATA_SCHEMA = vol.Schema( { 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 @@ class YaleConfigFlow(ConfigFlow, domain=DOMAIN): """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 @@ class YaleConfigFlow(ConfigFlow, domain=DOMAIN): 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 @@ class YaleConfigFlow(ConfigFlow, domain=DOMAIN): 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 a7c454851bf..fee5e5ab97a 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 bf8abe19af1..65894013adc 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 83c1ace569d..6f1b5ef6cf3 100644 --- a/homeassistant/components/solax/sensor.py +++ b/homeassistant/components/solax/sensor.py @@ -19,11 +19,12 @@ from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT, TEMP_CELSIUS 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 @@ class Inverter(SensorEntity): self, uid, serial, + version, key, unit, state_class=None, @@ -151,6 +156,12 @@ class Inverter(SensorEntity): 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 592db241d21..420ebca7463 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 78190aff5b3..81127f0dff2 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 00000000000..cbf1e2ae7c9 --- /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 00000000000..6fa4bed7b54 --- /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 b5f02d5b3d5..d322f291553 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 bca9ff03471..afee50a6b00 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 4a720d6cb77..25b0fe796da 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 ea40df76ae7..82d64eff575 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 4d2390d89c5..0a7db1888f6 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 7a3523ede16..60de0710e40 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 bab70cd4b94..d455b7befe6 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 abdbbf1191a..9a4444c523e 100644 --- a/homeassistant/components/wiz/__init__.py +++ b/homeassistant/components/wiz/__init__.py @@ -5,7 +5,6 @@ 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 @@ -16,7 +15,13 @@ 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 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 aa564a14f33..924e88793e4 100644 --- a/homeassistant/components/wiz/config_flow.py +++ b/homeassistant/components/wiz/config_flow.py @@ -15,7 +15,7 @@ from homeassistant.const import CONF_HOST 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 e1a98482635..d1b3a0f6251 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 @@ WIZ_EXCEPTIONS = ( 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 e76921b945b..e6e31f08713 100644 --- a/homeassistant/components/roku/__init__.py +++ b/homeassistant/components/roku/__init__.py @@ -24,6 +24,7 @@ PLATFORMS = [ 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 7aed3849ce8..d8cd540e613 100644 --- a/homeassistant/components/roku/browse_media.py +++ b/homeassistant/components/roku/browse_media.py @@ -21,6 +21,7 @@ from homeassistant.core import HomeAssistant 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 00000000000..7f507a9fe52 --- /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 619672e8f1f..d68f2b4b242 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 48b85f5912c..ff9e034e5d4 100644 --- a/homeassistant/components/roku/media_player.py +++ b/homeassistant/components/roku/media_player.py @@ -58,6 +58,7 @@ from .const import ( ) from .coordinator import RokuDataUpdateCoordinator from .entity import RokuEntity +from .helpers import format_channel_name _LOGGER = logging.getLogger(__name__) @@ -212,10 +213,9 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity): 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 00000000000..9120a4fe9ce --- /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 b2a15ae0064..394dd8099bc 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 03a713478e6..4f81c60171c 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 16261e07a89..677a10c697c 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 42181b08745..17c29ace2de 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 d551a548c4c..24f92b0b11b 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 99d0d1bb2c0..f5a3d270f70 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 a039b313702..2686a281dba 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 00000000000..e82a13c8511 --- /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 6ca27635d30..983455255fa 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 85513378ed7..1083b80ac47 100644 --- a/homeassistant/components/nest/__init__.py +++ b/homeassistant/components/nest/__init__.py @@ -158,6 +158,8 @@ class SignalUpdateCallback: "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 10983768e17..752ab0e5069 100644 --- a/homeassistant/components/nest/events.py +++ b/homeassistant/components/nest/events.py @@ -25,7 +25,8 @@ NEST_EVENT = "nest_event" # "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 ee286242a8c..0ab387a7dea 100644 --- a/tests/components/nest/test_events.py +++ b/tests/components/nest/test_events.py @@ -28,7 +28,7 @@ NEST_EVENT = "nest_event" 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 e8017e7d849..0963a4f927e 100644 --- a/homeassistant/components/sensibo/climate.py +++ b/homeassistant/components/sensibo/climate.py @@ -157,7 +157,7 @@ class SensiboClimate(CoordinatorEntity, ClimateEntity): 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 @@ class SensiboClimate(CoordinatorEntity, ClimateEntity): 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 @@ class SensiboClimate(CoordinatorEntity, ClimateEntity): 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 @@ class SensiboClimate(CoordinatorEntity, ClimateEntity): 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 9509be88266..41c44e741e9 100644 --- a/homeassistant/components/sensibo/coordinator.py +++ b/homeassistant/components/sensibo/coordinator.py @@ -72,7 +72,17 @@ class SensiboDataUpdateCoordinator(DataUpdateCoordinator): ) 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 @@ class SensiboDataUpdateCoordinator(DataUpdateCoordinator): "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 0ada739763f..a4819a124c9 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 00000000000..f4e95a99f56 --- /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 3784e2830d7..82ab670f8db 100644 --- a/homeassistant/components/homekit_controller/config_flow.py +++ b/homeassistant/components/homekit_controller/config_flow.py @@ -154,34 +154,16 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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") - return self.async_abort(reason="no_devices") + self.name = device.info["name"].replace("._hap._tcp.local.", "") + self.model = device.info["md"] + self.hkid = normalize_hkid(device.info["id"]) + + 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 ce7ae876e03..d156ae14256 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 394dd8099bc..b0c4267de7b 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 4f81c60171c..3fac8b4b859 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 8a1f77d0e4c..101e4ebd024 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 e4ad06f322a..9642d5d3bc5 100644 --- a/homeassistant/components/homekit_controller/connection.py +++ b/homeassistant/components/homekit_controller/connection.py @@ -378,7 +378,7 @@ class HKDevice: 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 @@ class HKDevice: 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 d156ae14256..4e216474ec0 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 b0c4267de7b..7740a0363a1 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 3fac8b4b859..f9fd89fa205 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 | 110 ++++++++++------------ tests/components/sql/test_sensor.py | 121 ++++++++++++++++++++++++- 2 files changed, 167 insertions(+), 64 deletions(-) diff --git a/homeassistant/components/sql/sensor.py b/homeassistant/components/sql/sensor.py index b9e3b9ce81d..1c8e87051be 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 @@ from sqlalchemy.orm import scoped_session, sessionmaker 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 @@ CONF_QUERY = "query" 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 @@ _QUERY_SCHEME = vol.Schema( } ) -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 + self._attr_extra_state_attributes = {} - @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): + 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 @@ class SQLSensor(SensorEntity): 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 629ec464e58..0e543f98a21 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 -async def test_query(hass): + +@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: 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 f59bfad92f4..9ad295d9ac5 100644 --- a/homeassistant/components/telnet/switch.py +++ b/homeassistant/components/telnet/switch.py @@ -4,6 +4,7 @@ from __future__ import annotations from datetime import timedelta import logging import telnetlib +from typing import Any import voluptuous as vol @@ -26,6 +27,7 @@ from homeassistant.const import ( 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 4f98818d9b3..1bcaaaa60a6 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 @@ import subprocess _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 e7cfe5288d8..b2c8b29478a 100644 --- a/homeassistant/components/command_line/binary_sensor.py +++ b/homeassistant/components/command_line/binary_sensor.py @@ -23,6 +23,7 @@ from homeassistant.core import HomeAssistant 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 @@ class CommandBinarySensor(BinarySensorEntity): 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 ff00138eba4..321b18437d9 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 @@ from homeassistant.core import HomeAssistant 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 @@ class CommandCover(CoverEntity): 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 @@ class CommandCover(CoverEntity): 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 @@ class CommandCover(CoverEntity): 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 1086c6300c2..6f364947775 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 @@ import voluptuous as vol 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 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( ) -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 387dacfc8de..5dbbbf88e58 100644 --- a/homeassistant/components/command_line/sensor.py +++ b/homeassistant/components/command_line/sensor.py @@ -19,10 +19,10 @@ from homeassistant.const import ( ) 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 @@ class CommandSensor(SensorEntity): 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 @@ class CommandSensorData: 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 e65db8afcc8..ff0d9b65f9d 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 @@ from homeassistant.core import HomeAssistant 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 @@ class CommandSwitch(SwitchEntity): 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 @@ class CommandSwitch(SwitchEntity): 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 @@ class CommandSwitch(SwitchEntity): ) @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 @@ class CommandSwitch(SwitchEntity): ) 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 532e14573f4..b523b02aa64 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 9d4f5b60c8b..98c917f51ba 100644 --- a/tests/components/command_line/test_cover.py +++ b/tests/components/command_line/test_cover.py @@ -6,6 +6,8 @@ import tempfile 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 561ac07df20..26b53a827e7 100644 --- a/tests/components/command_line/test_notify.py +++ b/tests/components/command_line/test_notify.py @@ -7,6 +7,8 @@ import tempfile 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 c9a4860b987..62ec1dbe97b 100644 --- a/tests/components/command_line/test_sensor.py +++ b/tests/components/command_line/test_sensor.py @@ -4,6 +4,8 @@ from __future__ import annotations 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 f918c7500ad..307974ab3fe 100644 --- a/tests/components/command_line/test_switch.py +++ b/tests/components/command_line/test_switch.py @@ -8,6 +8,8 @@ import tempfile 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 b2c9cd82f5a..0b1a5a94da0 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 7740a0363a1..7b5397cb44d 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 f9fd89fa205..325b6f8f554 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 d17d1f4d9ef..e72af839e4c 100644 --- a/homeassistant/components/fritz/button.py +++ b/homeassistant/components/fritz/button.py @@ -12,10 +12,9 @@ from homeassistant.components.button import ( 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 @@ BUTTONS: Final = [ 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 b58f3311cb5..b80d853e562 100644 --- a/homeassistant/components/fritzbox/binary_sensor.py +++ b/homeassistant/components/fritzbox/binary_sensor.py @@ -13,8 +13,8 @@ from homeassistant.components.binary_sensor import ( 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 @@ BINARY_SENSOR_TYPES: Final[tuple[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 @@ BINARY_SENSOR_TYPES: Final[tuple[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 06a31a4e10a..80c7885f26c 100644 --- a/homeassistant/components/goodwe/number.py +++ b/homeassistant/components/goodwe/number.py @@ -9,9 +9,9 @@ from goodwe import Inverter, InverterError 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 @@ NUMBERS = ( 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 @@ NUMBERS = ( 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 985c799110d..c8fa44b7e26 100644 --- a/homeassistant/components/goodwe/select.py +++ b/homeassistant/components/goodwe/select.py @@ -5,9 +5,8 @@ from goodwe import Inverter, InverterError 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 @@ OPERATION_MODE = SelectEntityDescription( 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 c2a11386cf4..e9a09f9db86 100644 --- a/homeassistant/components/homewizard/sensor.py +++ b/homeassistant/components/homewizard/sensor.py @@ -16,13 +16,12 @@ from homeassistant.const import ( 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 @@ SENSORS: Final[tuple[SensorEntityDescription, ...]] = ( 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 @@ SENSORS: Final[tuple[SensorEntityDescription, ...]] = ( 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 034573299e6..23ea5124e61 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 35133b55bd1..e924a735e0f 100644 --- a/homeassistant/components/vicare/button.py +++ b/homeassistant/components/vicare/button.py @@ -14,8 +14,8 @@ import requests 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 @@ BUTTON_DESCRIPTIONS: tuple[ViCareButtonEntityDescription, ...] = ( 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 90148ba42f3..abfa94f5906 100644 --- a/homeassistant/components/zha/button.py +++ b/homeassistant/components/zha/button.py @@ -8,9 +8,10 @@ from typing import Any 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 @@ class ZHAIdentifyButton(ZHAButton): 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 ff76023d96d..7cb214566d1 100644 --- a/homeassistant/components/zha/select.py +++ b/homeassistant/components/zha/select.py @@ -8,9 +8,10 @@ from zigpy.zcl.clusters.security import IasWd 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 eb79634695f..1e7d4f28a38 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -25,7 +25,6 @@ from homeassistant.const import ( 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 | 89 +++++----- requirements_test_all.txt | 3 + tests/components/scrape/__init__.py | 83 +++++++++ tests/components/scrape/test_sensor.py | 206 ++++++++++++++++++++++ 6 files changed, 334 insertions(+), 49 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 a4819a124c9..5fa34f27656 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 b7b688a0e73..ceefad2bad4 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 c0523e4cbe2..8f2a672ef06 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.rest.data import RestData 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.core import HomeAssistant 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 @@ CONF_INDEX = "index" 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) + 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 := config.get(CONF_VALUE_TEMPLATE)) is not None: + 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 @@ class ScrapeSensor(SensorEntity): 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 @@ class ScrapeSensor(SensorEntity): _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 325b6f8f554..10bb8f5af4d 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 00000000000..0ba9266a79d --- /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 00000000000..aaf156208ef --- /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 5fa34f27656..de48567eb6c 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 00000000000..896a3240a22 --- /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 604e4daaf35..8d7d76dc3db 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 06d1940f23d..93a24520497 100644 --- a/homeassistant/generated/zeroconf.py +++ b/homeassistant/generated/zeroconf.py @@ -391,7 +391,12 @@ HOMEKIT = { "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 98c5e277f17..009b8e5c775 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 7b5397cb44d..9186eeed738 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 10bb8f5af4d..b9ece9bf5cf 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 ac5ca313f2f..ce631297db6 100644 --- a/homeassistant/components/ebusd/const.py +++ b/homeassistant/components/ebusd/const.py @@ -191,7 +191,7 @@ SENSOR_TYPES = { 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 fb66622d893..59ebf1ead55 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 ae7c24e80d2..efaa07b3172 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 f6b229e99fd..2b62fdd78c4 100644 --- a/homeassistant/components/spotify/media_player.py +++ b/homeassistant/components/spotify/media_player.py @@ -7,7 +7,7 @@ from datetime import timedelta 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 @@ class SpotifyMediaPlayer(MediaPlayerEntity): 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 | 35 +++- homeassistant/components/picnic/strings.json | 6 +- .../components/picnic/translations/en.json | 7 +- tests/components/picnic/test_config_flow.py | 194 ++++++++++++++++-- 4 files changed, 207 insertions(+), 35 deletions(-) diff --git a/homeassistant/components/picnic/config_flow.py b/homeassistant/components/picnic/config_flow.py index 09a1d524283..c2d48ca9415 100644 --- a/homeassistant/components/picnic/config_flow.py +++ b/homeassistant/components/picnic/config_flow.py @@ -9,6 +9,7 @@ import requests 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): _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() + 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"]) - return self.async_create_entry( - title=info["title"], - data={ - CONF_ACCESS_TOKEN: auth_token, - CONF_COUNTRY_CODE: user_input[CONF_COUNTRY_CODE], - }, - ) + # 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 7fbd5e9bef6..9eb51b2fd2a 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 c7097df12a9..13b62c78757 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 b6bcc17a03d..3ea54cee593 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 96c04597d89..9a0cd6f51e3 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 @@ class RokuRemote(RokuEntity, RemoteEntity): 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 adfe15b7a3c..4e9a2b39d1b 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 @@ from homeassistant.components.notify import ( 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 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( ) -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 b4822c7bcd5..e69a7701eb9 100644 --- a/homeassistant/components/file/sensor.py +++ b/homeassistant/components/file/sensor.py @@ -16,6 +16,7 @@ from homeassistant.const import ( 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 @@ class FileSensor(SensorEntity): 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 9650c6945a6..5c91460237e 100644 --- a/tests/components/file/test_notify.py +++ b/tests/components/file/test_notify.py @@ -4,15 +4,16 @@ from unittest.mock import call, mock_open, patch 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 99e08362ab7..97fe6250d02 100644 --- a/tests/components/file/test_sensor.py +++ b/tests/components/file/test_sensor.py @@ -4,6 +4,7 @@ from unittest.mock import Mock, mock_open, patch 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 6208091dd8d..f347a5187ea 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 88cdf893d4a..2646fecfd37 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 f93ba4fbb93..3f29c3a2e67 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 8e7f6fcf294..fbc1d2642ea 100644 --- a/homeassistant/components/sonos/helpers.py +++ b/homeassistant/components/sonos/helpers.py @@ -24,7 +24,7 @@ UID_POSTFIX = "01400" _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 db10d9189e5..4ee3253c889 100644 --- a/homeassistant/components/sonos/switch.py +++ b/homeassistant/components/sonos/switch.py @@ -3,6 +3,7 @@ from __future__ import annotations import datetime import logging +from typing import Any from soco.exceptions import SoCoSlaveException, SoCoUPnPException @@ -337,19 +338,19 @@ class SonosAlarmEntity(SonosEntity, SwitchEntity): 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 4886bbac621..d5410bf232c 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -331,11 +331,19 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator): _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 79506c0bda2..be166d13455 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -312,7 +312,9 @@ class Stream: 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 @@ class Stream: 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 697dc0cf4c4..e583aad904b 100644 --- a/homeassistant/components/amcrest/binary_sensor.py +++ b/homeassistant/components/amcrest/binary_sensor.py @@ -65,10 +65,6 @@ _MOTION_DETECTED_EVENT_CODE = "VideoMotion" _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 @@ BINARY_SENSORS: tuple[AmcrestSensorEntityDescription, ...] = ( 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 | 27 +++++++++++- .../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, 585 insertions(+), 32 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 37713ec0634..c457988681b 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 d987ee472e2..e543a83bfe5 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 e85bab17ac0..eb49e33c019 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": { - "user": { + "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 ff6445f0b72..d076ad684c2 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 de51e67b206..c3efd45dbc0 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 00000000000..3784dae202d --- /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 00000000000..b307c6e79fc --- /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 7b2e7958308..939d07c27aa 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 00000000000..599bcbc771e --- /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 cadbd1aa4b8..22f3515b497 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 00000000000..4985fb4770b --- /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 1ca3be234e5..9355e0d6b80 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 5af782e4e46..10f2d6d70fa 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 aef27609568..432f4405c03 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 00000000000..1699e0f8e19 --- /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 00000000000..dfe114ef068 --- /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 00000000000..1a0e5f47ccf --- /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 00000000000..0ac34b3153f --- /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 00000000000..2105be9fc89 --- /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 64452c9ef58..cf8d31ec389 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 22ac348af4e..7333a0cd0ee 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 e824d89da07..ef7cacaaf96 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 bbf2b15d13e..e978a8f36f2 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 e76f534350b..d33dd5bb44c 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 c81d180aef0..292ae6a8539 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 1a11e00664c..65b10f61df3 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 13b62c78757..06b3018f88e 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 11fc0f1fe88..41f5018079c 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 819125c6909..db97b991f6f 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 5379949aa96..194cffd7e6a 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 c11bc3fa965..b864d13923d 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 e754faf8a0e..9d7a7fbdb23 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 f090e85c0cf..c4a69c402c4 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 a59a3efc089..007d4e9e86b 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 256ae5aa6a8..6359d66f50a 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 ec05565b9a6..7b8528476e1 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 2e792646cec..eacaecacb07 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 613ce8cedd2..23a1d29adeb 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 979a8d50ee1..e645694ae55 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 00000000000..73e5af2fc79 --- /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 91cd93d627e..743c7ffc97e 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 68092c434a3..256989b83dc 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 b40d9ef8680..ed2e934cbec 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 00000000000..dd091bd10eb --- /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 00000000000..d26658e666a --- /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 e99e8e7e14c..d4b1fc92ed8 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 c0108fc32c1..97ffaecd0f2 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 fed43b2d96f..ce088267106 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 0efbe56e3e0..5f30617d7eb 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 00000000000..5689db627e2 --- /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 00000000000..679604d6cfa --- /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 00000000000..a2356ed1035 --- /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 be166d13455..365ea946f51 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -342,7 +342,7 @@ class Stream: 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 @@ class Stream: 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 c754903965a..407b144267b 100644 --- a/tests/components/stream/conftest.py +++ b/tests/components/stream/conftest.py @@ -16,7 +16,8 @@ from collections import deque 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 0492fec14f0..a2bb328826d 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 @@ async def test_stream_keepalive(hass, setup_component): 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 @@ async def test_stream_keepalive(hass, setup_component): ): 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 b54c8dc3472..e5477c66f52 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 82ab670f8db..48551129b67 100644 --- a/homeassistant/components/homekit_controller/config_flow.py +++ b/homeassistant/components/homekit_controller/config_flow.py @@ -125,10 +125,9 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): # 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 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): # 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 4e216474ec0..43b0a614f44 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 9186eeed738..7ef4a52b8cb 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 b9ece9bf5cf..a1210060d6b 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 101e4ebd024..54408e5882d 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 3960d218423..530d45750f5 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 7ef4a52b8cb..3f200675ad2 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 a1210060d6b..330f03b7b9c 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 8f9a18d1f11..532bb25cbc8 100644 --- a/homeassistant/components/ovo_energy/sensor.py +++ b/homeassistant/components/ovo_energy/sensor.py @@ -54,7 +54,9 @@ SENSOR_TYPES_ELECTRICITY: tuple[OVOEnergySensorEntityDescription, ...] = ( 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 @@ SENSOR_TYPES_GAS: tuple[OVOEnergySensorEntityDescription, ...] = ( 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 365ea946f51..66929fff79c 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -344,7 +344,9 @@ class Stream: 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 36ac5cc3dde..4d4fce6e685 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -446,13 +446,31 @@ class EntityRegistry: 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 @@ class EntityRegistry: 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 714ac037e2a..78c99640bd0 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 48551129b67..d41eb0ed220 100644 --- a/homeassistant/components/homekit_controller/config_flow.py +++ b/homeassistant/components/homekit_controller/config_flow.py @@ -36,8 +36,6 @@ HOMEKIT_IGNORE = [ 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 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 43b0a614f44..44de321db4a 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 3f200675ad2..14d97bec67a 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 330f03b7b9c..d8c0eeaab49 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 54408e5882d..a65d63b1af2 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 1296cf50d1b..3aa137f2460 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 14d97bec67a..65520ab40b9 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 d8c0eeaab49..771dcc8cf87 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 e714b6b6b31..396ab56b8c2 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 089ef5e0ab8..b5c6b1a3e24 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 78ad9b25cd7..ffe4140a434 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 or ip_addr.version == 6: + 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 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 dc007d5c2c5..b7e99991fdd 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 76ffcc42bd1..eb1b50db5b6 100644 --- a/homeassistant/components/opengarage/__init__.py +++ b/homeassistant/components/opengarage/__init__.py @@ -66,7 +66,7 @@ class OpenGarageDataUpdateCoordinator(update_coordinator.DataUpdateCoordinator): 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 320ce092427..f263d79e2db 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -126,6 +126,8 @@ def cmd( 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 @@ class LgWebOSMediaPlayerEntity(RestoreEntity, MediaPlayerEntity): """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 @@ class LgWebOSMediaPlayerEntity(RestoreEntity, MediaPlayerEntity): 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 @@ class LgWebOSMediaPlayerEntity(RestoreEntity, MediaPlayerEntity): 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 @@ class LgWebOSMediaPlayerEntity(RestoreEntity, MediaPlayerEntity): 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 @@ class LgWebOSMediaPlayerEntity(RestoreEntity, MediaPlayerEntity): 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 + return self._supported_features | SUPPORT_TURN_ON - if self.state != STATE_OFF: - self._supported_features = supported - - 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 c9cc4a78aee..f0ebdd70e97 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 619dcfa9153..fff34120dcf 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 7613c561759..84197d3ef01 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 00000000000..6ca86ce0542 --- /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 c294fa7c86b..26cc502643a 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 789906c36e4..214605b1aa9 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 2ac0e09634c..a1f7f650821 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 60fd251d0a5..93e4bfa4262 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 e45564b1116..491ef12d920 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 88a9eb36f2f..c90ecfda925 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 06b0e44a70a..518f05e8ac3 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 c113c987cea..610a5a4f586 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 63210112cbb..ba3d5b98803 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 9c1644382e5..0fdcc207646 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 ff1f06ff6f8..6e3d2669c08 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 14bbe0e5dd8..4a6d26cd73d 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 46fecc87e2c..e36f0621cdc 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 e73f76235f0..99acd65a994 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 95becd2dc0a..69e1d1f1327 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 3c28dd6ca6a..995c16ed994 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 dda5e5c293c..685cbe686bb 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 00000000000..9cf028f4c83 --- /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 89e3f43be56..38182d67c4a 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 e5118e6f9ee..95625c2b8da 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 3feddeb7cd0..74ee4c61f97 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 8d71dd3070e..72b454e4e0b 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 fb3e4a5f6ab..ae6da0296c2 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 2f023dc3050..822a75ad7c7 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 c576116efb2..bb8a94df0cb 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 3210d36f1c8..92b697b7f00 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 8e2990982d6..46d31db307a 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 05560930fc3..87193a9adf7 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 34c3a0340b6..3cdd4500930 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 8da0b8dbd4e..436a00cbfda 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 f9852dcb286..c467239ea82 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 12bff6f7b6c..85731fbc8cf 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 00000000000..b1115d5bf2c --- /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 64eaa4c7733..3e8ec8ece2a 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 760a3a91365..9fad6c40d94 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 dd801a69f33..4cd16be65a3 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 00000000000..18c2f0869bd --- /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 8471b3b5f11..4c5dd46faf7 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 dd01e64ac64..9a87960381a 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 7022315d9c4..43fdcaa4764 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 8ff8b1324c4..86343321e18 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 f3bd4c2d4c0..21db3ef18e0 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 d6841e0f81b..62d94fbb68b 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 1f22f20b3a2..611a3ebce8e 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 8a9f2a08ba4..d159c86efaa 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 c6bb137e571..4b2d98674c5 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 8e5504b3ee4..544d6cfda68 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 5d5d0dcf9a5..6d14401f970 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 00000000000..18c2f0869bd --- /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 6c4a3e36836..7de4c531d6a 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 7fdd2f6c2c5..34e61e0a1ae 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 e76e50d1caf..7da30f4a1be 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 00000000000..18c2f0869bd --- /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 7f409ca75d6..3b1b60ebae3 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 f974693fd2a..00d9a264883 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 00000000000..b1115d5bf2c --- /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 d6deb0e84c6..765a9f5d3b6 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 36320b28606..30d1c5e01e7 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 00000000000..b1115d5bf2c --- /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 e019c6ac5ef..88a0aeb68c8 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 ea468429924..3fd7a86e99d 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 00000000000..4dee8082dbf --- /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 00000000000..18c2f0869bd --- /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 d00e805ad1d..99d75f26cc0 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 53d33e27e8f..c6929ecbbf2 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 ea7fe194fef..f08f75f2da9 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 805490698bb..d037c2e07fd 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 48edc219315..aa6c808eec7 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 6cb6a0d4ea0..c369993fa0d 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 57d3902c1f0..ba346a19acd 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 14fc1ef094f..26ad68c7d5b 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 d3ec06dcbfb..4b5c69c6101 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 1976c7b8fd4..7863f5ad5eb 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 00000000000..18c2f0869bd --- /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 8fc295ebf9a..0188d8032e6 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 eea8c3495b4..803b5e1231a 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 9ecdd19037f..bcda6d770a3 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 65335fe7c2b..7e37a1c1f9d 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 83aba4ae98a..0304a1423f6 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 ed475f6c0dd..29ffda9a5e9 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 179004c8e43..447231bcb0c 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 fe686797d77..44941e91084 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 cbf1e2ae7c9..48e3fdefc60 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 d15111e97b0..a044d731d17 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 7c97f50a85f..205436aa03d 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 29fa43b4a16..50851e08956 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 845e6949c01..110dc2a5c05 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 68e185b7339..4cf45560e8e 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 0edaee152ac..1f731e6efbd 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 06b4294bae9..cc054a26d97 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 f4b0af97896..3230b8df45f 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 288856ef8a1..24e5bde93c5 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 8e12bb837f4..cbe07cce0f7 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 2f13daba62c..80d36689553 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 394e8ca41ef..f8d2b4b460e 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 e29ff8dee5f..cf8474a3762 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 00000000000..18c2f0869bd --- /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 676e0e9ca89..dedd4eb3def 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 232e757b643..b81293d60a6 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 83243718739..790aafc0796 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 07b79c83c36..af9fd658ab9 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 8e60f589fc5..d1981d80c7e 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 00e89f9735d..b512655a7d7 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 de3abd8dca7..d3e7ea45706 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 11833024f9c..9992a30caea 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 5c1ab6ee04d..105be4965c4 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 00000000000..c0ccf23f5b5 --- /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 f931aa6f3c0..206fbdab2a2 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 03b5566566f..794a33ffe75 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 c70dcca0260..3841b7ddbaf 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 e77faae817d..209b6d6fdb9 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 2f72809d4fe..a82f4ace04d 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 debd53f9f8a..9039fcf27bd 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 e9e8ad9bf5a..45247108dc6 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 a6a6edab938..1f5d5ac22cd 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 221f35ebba6..a24f4954f97 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 8d986480ff0..4fb06d067dc 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 2012c853ddd..c2052edaab8 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 00000000000..50eb1dfd177 --- /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 ebf05b607dd..4ac4f3e8445 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 f1faffc85e1..dd977e96860 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 551ff120de7..cedcd1691cf 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 00000000000..18c2f0869bd --- /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 152051868bc..026160f21e1 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 d8f772f9e51..efa82925191 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 00000000000..1dbdef83eea --- /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 bc8b4094337..23d8a479b73 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 4c677795525..e146efbb749 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 c87fb65f446..e8f1b53c6ea 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 a7663fff6d6..06c6565c719 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 14c3fe70ac4..742ad113bb7 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 00000000000..18c2f0869bd --- /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 cdaa3e16590..1124fde09c4 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 a2e96925a5b..2a1f19d55fc 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 1a4a2627af3..7e626db563b 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 e392cd4e4ff..7f7e163cfbb 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 2716af6477b..deb451383f6 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 2f022cfb50e..1df0b2de9c3 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 95c4d883f02..f3c122a2f2c 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 00000000000..18c2f0869bd --- /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 8758610a4a2..e2abc370909 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 c74dcf759ef..15d2736c269 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 d22c90b4e10..f1c469f8e5f 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 539792754bc..a675c021cd3 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 503db024603..34766766828 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 327da665314..274b4b7d11a 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 a7911f7f907..068d8a80913 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 286fbaee105..65a346ccfbf 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 26e05764d2e..df42b440048 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 f4ccdf8b400..5499c9ae10d 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 32688c432c4..899af053888 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 e19942a36af..fc1a4535178 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 b25fbd9f5df..e69a3c00eed 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 27e26acabde..654095e2cfd 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 caebd296512..ab67be514bc 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 92171723b55..c5b01400457 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 13413fd102c..a8efc9d1e95 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 aad2455a882..a6782ec138a 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 1ba8c5aa5c6..efd0cb529b1 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 e906610d565..ed87fbbdf78 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 a4a16fd34ee..ec9ff8394b6 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 1b7397feadd..5105f19e2bc 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 a2eba2ffbc0..5ae560ddae8 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 a813fc3ede2..7d43245e617 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 e2c4495a77e..a5bd6ebdebe 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 da02cbb297f..376a62b9ff0 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 0a551fc8874..e1fd6af43bc 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 2336b3ded4f..c1d7d1676a9 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 115f2d4cdf8..d03b04c6261 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 d6628f9747c..bccb1c3aa3c 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 00000000000..18c2f0869bd --- /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 278dda51208..3741ef6a8f3 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 5d7bc400600..e6123a1d715 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 00000000000..18c2f0869bd --- /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 3d46faee2eb..2af575a32a1 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 b78e4a327a1..76065006684 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 a5118ffd849..d11e8bdc709 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 00000000000..1dbdef83eea --- /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 00000000000..1705796cf77 --- /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 e9cd89c635a..664a8ae7da8 100644 --- a/homeassistant/components/vizio/media_player.py +++ b/homeassistant/components/vizio/media_player.py @@ -145,7 +145,7 @@ class VizioDevice(MediaPlayerEntity): 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 @@ class VizioDevice(MediaPlayerEntity): 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 @@ class VizioDevice(MediaPlayerEntity): 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 @@ class VizioDevice(MediaPlayerEntity): @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 @@ class VizioDevice(MediaPlayerEntity): 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 d3ef4019c57..80f72280951 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 bd83e1505a2..bf98c8a449e 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 6c1eb62831c..f92cf0c8d30 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 00000000000..97676592f47 --- /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 00000000000..603739159af --- /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 b8d159893fe..5685b479d1c 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -349,7 +349,9 @@ class HomeAssistant: 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 ed90b5b893b..20819ac7504 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 e9038d1f658..bf2e13c1e24 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -674,7 +674,7 @@ class Entity(ABC): 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 da6732d05e7..a1dba0d6962 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -105,7 +105,7 @@ class EntityComponent: 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 f263d79e2db..95e0b059538 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -138,6 +138,7 @@ class LgWebOSMediaPlayerEntity(RestoreEntity, MediaPlayerEntity): """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 c31daa85316..9ee705c1c5e 100644 --- a/homeassistant/components/hdmi_cec/media_player.py +++ b/homeassistant/components/hdmi_cec/media_player.py @@ -26,7 +26,7 @@ from pycec.const import ( 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.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 = 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 8e6deae1394..a5d64b2a7fa 100644 --- a/homeassistant/components/hdmi_cec/switch.py +++ b/homeassistant/components/hdmi_cec/switch.py @@ -3,17 +3,17 @@ from __future__ import annotations 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 20ea057da6e..f3f253fe530 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 . import FiveMEntity, FiveMEntityDescription 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 a230208a04e..cbc5df64496 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 65520ab40b9..89a326e672d 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 771dcc8cf87..c12edbf2850 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 374a8957bcc..b07dc04cd15 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 bb168fce09f..764138ca5f3 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 @@ class HomeAssistantHTTP: 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 b50555b9841..292c46e55f9 100644 --- a/homeassistant/components/http/ban.py +++ b/homeassistant/components/http/ban.py @@ -6,7 +6,7 @@ from collections.abc import Awaitable, Callable 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 ff50e9bd965..c0aaa31fab0 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 @@ _LOGGER = logging.getLogger(__name__) @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 59ebf1ead55..c057ea240c0 100644 --- a/homeassistant/components/spotify/__init__.py +++ b/homeassistant/components/spotify/__init__.py @@ -30,7 +30,11 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda 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 @@ PLATFORMS = [Platform.MEDIA_PLAYER] __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 efaa07b3172..db2379a57fc 100644 --- a/homeassistant/components/spotify/browse_media.py +++ b/homeassistant/components/spotify/browse_media.py @@ -6,11 +6,13 @@ import logging 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 cdb8e933523..7f7f682fb9e 100644 --- a/homeassistant/components/spotify/util.py +++ b/homeassistant/components/spotify/util.py @@ -3,6 +3,8 @@ from __future__ import annotations 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 49282c70cb0..a58280158d8 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -3,7 +3,7 @@ from __future__ import annotations 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 4020601dc3f..4b64e028f97 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -3,8 +3,9 @@ from __future__ import annotations 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 5c56cb55b19..7b2f963102e 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -4,6 +4,7 @@ from __future__ import annotations 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 9a4444c523e..40dc4cf70d1 100644 --- a/homeassistant/components/wiz/__init__.py +++ b/homeassistant/components/wiz/__init__.py @@ -4,13 +4,15 @@ from datetime import timedelta 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 @@ from .const import ( DISCOVER_SCAN_TIMEOUT, DISCOVERY_INTERVAL, DOMAIN, + SIGNAL_WIZ_PIR, WIZ_CONNECT_EXCEPTIONS, WIZ_EXCEPTIONS, ) @@ -27,7 +30,7 @@ from .models import WizData _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_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ), ) - 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 00000000000..1ecb3125215 --- /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 d1b3a0f6251..1aeb2ada580 100644 --- a/homeassistant/components/wiz/const.py +++ b/homeassistant/components/wiz/const.py @@ -21,3 +21,5 @@ WIZ_EXCEPTIONS = ( 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 1ddaced401f..82f19a61002 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 @@ class WizToggleEntity(CoordinatorEntity, ToggleEntity): 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 57650ede272..931dd5ec18c 100644 --- a/tests/components/wiz/__init__.py +++ b/tests/components/wiz/__init__.py @@ -13,6 +13,7 @@ from pywizlight.discovery import DiscoveredBulb 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 @@ FAKE_DIMMABLE_BULB = BulbType( 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 @@ def _mocked_wizlight(device, extended_white_range, bulb_type) -> wizlight: 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 _patch_discovery(): 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 00000000000..adfef066e16 --- /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 dc4bd4de329..f87f1b75437 100644 --- a/tests/components/wiz/test_config_flow.py +++ b/tests/components/wiz/test_config_flow.py @@ -12,7 +12,6 @@ from homeassistant.const import CONF_HOST 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 @@ from . import ( 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 88b6e447446..fdd5b02fe9b 100644 --- a/homeassistant/components/switcher_kis/const.py +++ b/homeassistant/components/switcher_kis/const.py @@ -8,7 +8,7 @@ DATA_BRIDGE = "bridge" 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_SET_AUTO_OFF_NAME = "set_auto_off" 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 565961e1b8c..74ca65ade06 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -411,7 +411,7 @@ class CastDevice(MediaPlayerEntity): # 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 36ee2ce818a..cf72cf38827 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 c74860c7f64..52f7cba7a19 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 89a326e672d..388f335b96d 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 c12edbf2850..0464e14c2a3 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 82457d5b296..ed8a45822b0 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 @@ class RPiPowerFlow(DiscoveryFlowHandler, domain=DOMAIN): 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 07add8e6d7c..30778edc493 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 d7920f80941..fddc5c82725 100644 --- a/homeassistant/helpers/config_entry_flow.py +++ b/homeassistant/helpers/config_entry_flow.py @@ -3,7 +3,7 @@ from __future__ import annotations 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 @@ from .typing import UNDEFINED, DiscoveryInfoType, UndefinedType 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 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow): 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 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow): 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 a206f562c17..364a3d0db6a 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 b898efe49fe..b27ddbda382 100644 --- a/homeassistant/util/async_.py +++ b/homeassistant/util/async_.py @@ -11,14 +11,20 @@ import threading 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 fire_coroutine_threadsafe(coro: Coroutine, loop: AbstractEventLoop) -> 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_threadsafe( 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 bbab7d20f80..8372a98d332 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 81c629529df..3be5bf040d7 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 66e6bdd8379..d5e1671c135 100644 --- a/homeassistant/components/media_source/local_source.py +++ b/homeassistant/components/media_source/local_source.py @@ -276,7 +276,7 @@ class UploadMediaView(HomeAssistantView): 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 5b25e878e5a..7aae72475cb 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 2ba70ca9489..c4741672186 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 c0c0db85cf3..64df7260c5b 100644 --- a/homeassistant/components/vicare/climate.py +++ b/homeassistant/components/vicare/climate.py @@ -19,6 +19,7 @@ from homeassistant.components.climate.const import ( HVAC_MODE_OFF, PRESET_COMFORT, PRESET_ECO, + PRESET_NONE, SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE, ) @@ -87,11 +88,13 @@ HA_TO_VICARE_HVAC_HEATING = { 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 @@ class ViCareClimate(ClimateEntity): @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 @@ class ViCareClimate(ClimateEntity): ) _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 | 173 +++++++++++++++++++-- tests/components/wiz/test_switch.py | 66 ++++++++ 9 files changed, 288 insertions(+), 43 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 de48567eb6c..e85278586dd 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 c7ee612c6a9..0b7015643ff 100644 --- a/homeassistant/components/wiz/discovery.py +++ b/homeassistant/components/wiz/discovery.py @@ -17,17 +17,11 @@ _LOGGER = logging.getLogger(__name__) 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 ef60deea956..9b2d7e6fab4 100644 --- a/homeassistant/components/wiz/light.py +++ b/homeassistant/components/wiz/light.py @@ -32,6 +32,8 @@ from .const import DOMAIN 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 @@ class WizBulbEntity(WizToggleEntity, LightEntity): 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 @@ class WizBulbEntity(WizToggleEntity, LightEntity): 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 3aa137f2460..e333691d20c 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 931dd5ec18c..e553593bf2f 100644 --- a/tests/components/wiz/__init__.py +++ b/tests/components/wiz/__init__.py @@ -204,7 +204,7 @@ def _patch_discovery(): 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 f87f1b75437..f8426ece56d 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 00000000000..6411146d162 --- /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 16d7c6a0a5d..c79cf74e130 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 00000000000..e728ff4a645 --- /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 40dc4cf70d1..1bed875d02f 100644 --- a/homeassistant/components/wiz/__init__.py +++ b/homeassistant/components/wiz/__init__.py @@ -30,7 +30,7 @@ from .models import WizData _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 00000000000..eed9bd92803 --- /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 e553593bf2f..8d187e9b476 100644 --- a/tests/components/wiz/__init__.py +++ b/tests/components/wiz/__init__.py @@ -170,6 +170,7 @@ def _mocked_wizlight(device, extended_white_range, bulb_type) -> wizlight: 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 00000000000..a1ab5e6bbae --- /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 841a9365320..b0161a58251 100644 --- a/homeassistant/components/camera/media_source.py +++ b/homeassistant/components/camera/media_source.py @@ -47,8 +47,18 @@ class CameraMediaSource(MediaSource): 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 @@ class CameraMediaSource(MediaSource): 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 d5d65296e65..3a3558419e5 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 a58280158d8..fbe7a6b005f 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -59,7 +59,7 @@ COOLDOWN_TIME = 60 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 @@ LOGGING_INTEGRATIONS = { # 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 1bed875d02f..7bea86d323c 100644 --- a/homeassistant/components/wiz/__init__.py +++ b/homeassistant/components/wiz/__init__.py @@ -8,8 +8,8 @@ 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, 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_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ), ) + 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 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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 6411146d162..fb21e930efd 100644 --- a/tests/components/wiz/test_init.py +++ b/tests/components/wiz/test_init.py @@ -3,6 +3,7 @@ import datetime 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 515dc25899a..abe3d29c607 100644 --- a/homeassistant/components/tts/__init__.py +++ b/homeassistant/components/tts/__init__.py @@ -9,6 +9,7 @@ import io import logging import mimetypes import os +from pathlib import Path import re from typing import TYPE_CHECKING, Optional, cast @@ -39,10 +40,11 @@ import homeassistant.helpers.config_validation as cv 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 @@ CONF_FIELDS = "fields" 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 @@ class SpeechManager: 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 00000000000..492e995b87f --- /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 00000000000..2398a6203ad --- /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 5690591ccd2..9e09ebb9ff2 100644 --- a/tests/components/google_translate/test_tts.py +++ b/tests/components/google_translate/test_tts.py @@ -16,7 +16,7 @@ from homeassistant.config import async_process_ha_core_config 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 3580880fedb..6d995978391 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 3cbc1f0da00..9f1cc849a1f 100644 --- a/tests/components/tts/test_init.py +++ b/tests/components/tts/test_init.py @@ -5,6 +5,7 @@ from unittest.mock import PropertyMock, patch 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 @@ from homeassistant.components.media_player.const import ( 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 00000000000..3bfd204a228 --- /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 9989b1d349e..912896dd3e2 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 424bbe5e065..4eab1868057 100644 --- a/tests/components/voicerss/test_tts.py +++ b/tests/components/voicerss/test_tts.py @@ -15,7 +15,7 @@ import homeassistant.components.tts as tts 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 db4dbce4b8b..495009eecf9 100644 --- a/tests/components/yandextts/test_tts.py +++ b/tests/components/yandextts/test_tts.py @@ -14,7 +14,7 @@ import homeassistant.components.tts as tts 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 b07dc04cd15..318a4387510 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 4d4d1882dcd..da0a7f1dd2b 100644 --- a/homeassistant/components/fjaraskupan/config_flow.py +++ b/homeassistant/components/fjaraskupan/config_flow.py @@ -9,6 +9,7 @@ from bleak.backends.device import BLEDevice 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 @@ from .const import DOMAIN 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 bf6fc7cc334..d317fe6d873 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 06eebb45948..8fd651d1782 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 fc15cd833bc..47959e34074 100644 --- a/homeassistant/components/ios/config_flow.py +++ b/homeassistant/components/ios/config_flow.py @@ -3,4 +3,6 @@ from homeassistant.helpers import config_entry_flow 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 83a77e12579..bc4fb8ceddc 100644 --- a/homeassistant/components/izone/config_flow.py +++ b/homeassistant/components/izone/config_flow.py @@ -6,7 +6,7 @@ import logging 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 @@ from .discovery import async_start_discovery_service, async_stop_discovery_servi _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 f56688e919a..1f9c67b9aa1 100644 --- a/homeassistant/components/kulersky/config_flow.py +++ b/homeassistant/components/kulersky/config_flow.py @@ -3,6 +3,7 @@ import logging import pykulersky +from homeassistant.core import HomeAssistant from homeassistant.helpers import config_entry_flow from .const import DOMAIN @@ -10,7 +11,7 @@ from .const import DOMAIN _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 1713683b720..c48bee9e4e7 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 fdf17c14e5a..e68c51cd7eb 100644 --- a/homeassistant/components/zerproc/config_flow.py +++ b/homeassistant/components/zerproc/config_flow.py @@ -3,6 +3,7 @@ import logging import pyzerproc +from homeassistant.core import HomeAssistant from homeassistant.helpers import config_entry_flow from .const import DOMAIN @@ -10,7 +11,7 @@ from .const import DOMAIN _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 1264fa2c1df..cb90499b6ca 100644 --- a/pylint/plugins/hass_enforce_type_hints.py +++ b/pylint/plugins/hass_enforce_type_hints.py @@ -44,6 +44,8 @@ _MODULE_FILTERS: dict[str, re.Pattern] = { "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 @@ _METHOD_MATCH: list[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 835927e4627..87e8b98fa21 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 2f20e1404d8..976c66dda56 100644 --- a/homeassistant/helpers/translation.py +++ b/homeassistant/helpers/translation.py @@ -3,6 +3,7 @@ from __future__ import annotations 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 @@ class _TranslationCache: 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 @@ class _TranslationCache: 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 e85278586dd..5419a8247ad 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 e7bbc27141c..c00073b4432 100644 --- a/homeassistant/components/esphome/entry_data.py +++ b/homeassistant/components/esphome/entry_data.py @@ -19,6 +19,7 @@ from aioesphomeapi import ( EntityState, FanInfo, LightInfo, + LockInfo, NumberInfo, SelectInfo, SensorInfo, @@ -44,6 +45,7 @@ INFO_TYPE_TO_PLATFORM: dict[type[EntityInfo], str] = { 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 00000000000..84c93d9df13 --- /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 5419a8247ad..b59986fbe64 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 00000000000..f6015abd1f8 --- /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 c979ba05b5a..a88aa19d742 100644 --- a/homeassistant/auth/mfa_modules/totp.py +++ b/homeassistant/auth/mfa_modules/totp.py @@ -182,8 +182,8 @@ class TotpSetupFlow(SetupFlow): 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 @@ class TotpSetupFlow(SetupFlow): 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 a41fead628c..b3448d0b790 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 388f335b96d..ccc47cecb3f 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 0464e14c2a3..8a4a99bef14 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 2d971469928..2272ceb183f 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 e7ee76070a1..2763bd3fe42 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -522,6 +522,7 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity): """ 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 af44e621a7f..29d6ced184b 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 5272053a517..196bdfd055e 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 e1703b54acb..dc80126bd14 100644 --- a/homeassistant/components/iss/config_flow.py +++ b/homeassistant/components/iss/config_flow.py @@ -4,8 +4,10 @@ import voluptuous as vol 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 b9dd7c374d0..4a2da5f6556 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 f8ef8d27cd7..b90ff56964a 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 a20a8729f55..e47d977b96f 100644 --- a/tests/components/iss/test_config_flow.py +++ b/tests/components/iss/test_config_flow.py @@ -7,11 +7,12 @@ 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 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 9f6fa7fc35c..b81d675e6fd 100644 --- a/homeassistant/components/zwave_js/device_action.py +++ b/homeassistant/components/zwave_js/device_action.py @@ -53,6 +53,7 @@ from .const import ( 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 cfdb65a4b02..906efb2c4f9 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 fcd769dc8a4..ec8538e2b36 100644 --- a/homeassistant/components/zwave_js/device_condition.py +++ b/homeassistant/components/zwave_js/device_condition.py @@ -29,6 +29,7 @@ from .device_automation_helpers import ( 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 888efbf2bfd..0615668cccd 100644 --- a/homeassistant/components/zwave_js/device_trigger.py +++ b/homeassistant/components/zwave_js/device_trigger.py @@ -49,7 +49,11 @@ from .const import ( 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 5377d420268..07663ce9456 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 71a6865287c..da6b4b15459 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 bf3738a7fb3..8a792706461 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 7fb785d429e..0e1c6445a9f 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -15,6 +15,7 @@ from zwave_js_server.model.notification import ( ) 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 @@ from .helpers import ( 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_setup_entry( # noqa: C901 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_setup_entry( # noqa: C901 ) ) + # 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_setup_entry( # noqa: C901 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 @@ async def async_setup_entry( # noqa: C901 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 00000000000..ef8572fedc3 --- /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 3deb75cf761..2eb440cec90 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 76cb6fd22e9..840c36b7fde 100644 --- a/homeassistant/components/zwave_js/sensor.py +++ b/homeassistant/components/zwave_js/sensor.py @@ -61,7 +61,7 @@ from .discovery_data_template import ( 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 @@ class ZWaveNodeStatusSensor(SensorEntity): ) # 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 @@ class ZWaveNodeStatusSensor(SensorEntity): 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 ac3f233ba49..767516cc17c 100644 --- a/homeassistant/components/zwave_js/services.py +++ b/homeassistant/components/zwave_js/services.py @@ -457,7 +457,7 @@ class ZWaveServices: 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 @@ class ZWaveServices: 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 4f21f616ae1..d8fef11269c 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 00000000000..deb95e5eef4 --- /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 7e39b784533..d08c680dfe2 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 197654f489f..23b50c0939f 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 @@ SEASON_ICONS = { } -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 bbce2c985ed..ebbff8710f3 100644 --- a/homeassistant/components/overkiz/cover_entities/awning.py +++ b/homeassistant/components/overkiz/cover_entities/awning.py @@ -7,11 +7,11 @@ from pyoverkiz.enums import OverkizCommand, OverkizState 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 @@ from .generic_cover import COMMANDS_STOP, OverkizGenericCover 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 @@ class Awning(OverkizGenericCover): 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 60484620df1..c25cd1ab806 100644 --- a/homeassistant/components/overkiz/cover_entities/generic_cover.py +++ b/homeassistant/components/overkiz/cover_entities/generic_cover.py @@ -64,7 +64,7 @@ class OverkizGenericCover(OverkizEntity, CoverEntity): 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 6e69f24f2f1..12354f41241 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_OPEN = [OverkizCommand.OPEN, OverkizCommand.UP, OverkizCommand.CYCLE] 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 @@ class VerticalCover(OverkizGenericCover): ( 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 @@ class VerticalCover(OverkizGenericCover): 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 60614fcf97f..f6ba5a0393f 100644 --- a/homeassistant/components/sleepiq/__init__.py +++ b/homeassistant/components/sleepiq/__init__.py @@ -99,6 +99,11 @@ class SleepIQSensor(Entity): 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 f901851c0b5..f821a569254 100644 --- a/homeassistant/components/sleepiq/binary_sensor.py +++ b/homeassistant/components/sleepiq/binary_sensor.py @@ -41,7 +41,8 @@ class IsInBedBinarySensor(SleepIQSensor, BinarySensorEntity): """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 b892fc9a67f..d7e36c52517 100644 --- a/homeassistant/components/zha/climate.py +++ b/homeassistant/components/zha/climate.py @@ -756,3 +756,18 @@ class BecaThermostat(Thermostat): ) 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 2c0ce10a5f3..f8fbc0d010f 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 @@ class I2CHatBinarySensor(BinarySensorEntity): 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 @@ class I2CHatBinarySensor(BinarySensorEntity): 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 0e05e376ed4..8ca88528543 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 @@ class I2CHatSwitch(SwitchEntity): 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 @@ class I2CHatSwitch(SwitchEntity): 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 @@ class I2CHatSwitch(SwitchEntity): @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 e5a372176aa..82257805283 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 fe7f44123e9..df41c1d7b0a 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 c5f8f422b27..f280d6d6276 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 939d07c27aa..eb398cccff4 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 80a7cc489a9..1a52d69dc2d 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 9eb419288a8..a7aa5bb792b 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 05f6c83ad73..6d6b441484f 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 cf009baed45..3f6d005f045 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 7bf46c79e92..f0d8fdec84c 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 9ecd510ae99..dedf6ca570b 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 7e1a9be8e79..04ae0f6e9d5 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 b90ff56964a..56f9bd79e88 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 b4257ea668c..c1a78517fa8 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 c7296d1b565..dd7b165d2d4 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 0c74eb8b640..b62b4383fc6 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 45e3bcbb548..ffd38bce705 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 c278f29d13c..abf8a4f9469 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 e7125511ec3..4ae9d109df4 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 12be88bbd8c..4e46bd55033 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 7e46c32c859..e4ad1b3f231 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 cb9aea4f85d..28092bd8b8c 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 f46f1f6c452..2816ea011e4 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 e29b27e9026..654f8f2ee1b 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 fc0292523c2..23ef98366e4 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 ccc47cecb3f..4ed3c2ea7e0 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 8a4a99bef14..3b90ad19913 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 b0b2c4b1294..1c8de0c6544 100644 --- a/homeassistant/components/plugwise/coordinator.py +++ b/homeassistant/components/plugwise/coordinator.py @@ -6,6 +6,7 @@ from plugwise import Smile 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 @@ class PlugwiseDataUpdateCoordinator(DataUpdateCoordinator[PlugwiseData]): 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 bf9beae060c..525b4f3b43c 100644 --- a/homeassistant/components/utility_meter/__init__.py +++ b/homeassistant/components/utility_meter/__init__.py @@ -182,8 +182,6 @@ class TariffSelect(RestoreEntity): 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 b65628d5f0b..ec137968bc5 100644 --- a/homeassistant/components/utility_meter/sensor.py +++ b/homeassistant/components/utility_meter/sensor.py @@ -32,6 +32,7 @@ from homeassistant.helpers.event import ( 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 @@ class UtilityMeterSensor(RestoreEntity, SensorEntity): 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 @@ class UtilityMeterSensor(RestoreEntity, SensorEntity): 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 @@ class UtilityMeterSensor(RestoreEntity, SensorEntity): 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 @@ class UtilityMeterSensor(RestoreEntity, SensorEntity): 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 @@ class UtilityMeterSensor(RestoreEntity, SensorEntity): 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 61e6fc4dae8..3297c696ca1 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 51212580aaf..fbaf795f9e2 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 45ae6f90e6d..af2969f6f7a 100644 --- a/homeassistant/components/aussie_broadband/__init__.py +++ b/homeassistant/components/aussie_broadband/__init__.py @@ -5,14 +5,15 @@ from datetime import timedelta 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 fcec645127f..5476371f755 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 2ce8aaca9c4..04c1cff97b5 100644 --- a/homeassistant/components/aussie_broadband/sensor.py +++ b/homeassistant/components/aussie_broadband/sensor.py @@ -143,7 +143,7 @@ class AussieBroadandSensorEntity(CoordinatorEntity, SensorEntity): 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 4ed3c2ea7e0..8453c327c2f 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 3b90ad19913..d1d53c75756 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 abb4bce042d..abb99355ef3 100644 --- a/tests/components/aussie_broadband/common.py +++ b/tests/components/aussie_broadband/common.py @@ -5,7 +5,7 @@ from homeassistant.components.aussie_broadband.const import ( 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 @@ FAKE_SERVICES = [ "type": "PhoneMobile", "name": "Mobile", }, + { + "service_id": "23456789", + "description": "Fake ABB VOIP Service", + "type": "VOIP", + "name": "VOIP", + }, ] FAKE_DATA = { @@ -30,12 +36,16 @@ FAKE_DATA = { } -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 9e31aa9b737..9e2e0b7cccc 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 30fac808a27..c99c52d5c86 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 @@ MOCK_MOBILE_USAGE = { "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 3e55f6359c6..9a3a88dfc95 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 23ef98366e4..e26dcbe925a 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 8453c327c2f..29248f86cbc 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 d1d53c75756..f5a2fbf7a62 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 fca79bde040..2bd454fbe11 100644 --- a/homeassistant/components/nest/camera_sdm.py +++ b/homeassistant/components/nest/camera_sdm.py @@ -8,20 +8,16 @@ import logging 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 @@ class NestCamera(Camera): 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 a4539cf9f81..81b4a7cf0a6 100644 --- a/tests/components/nest/test_camera_sdm.py +++ b/tests/components/nest/test_camera_sdm.py @@ -7,7 +7,7 @@ pubsub subscriber. 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 @@ from homeassistant.components.camera import ( 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 @@ DOMAIN = "nest" 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 548e79e9157..d9b2a19d752 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 ab4b7929739..97bb7fc25ba 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 | 920 +++++++++++++----- 5 files changed, 849 insertions(+), 463 deletions(-) diff --git a/homeassistant/components/deconz/sensor.py b/homeassistant/components/deconz/sensor.py index 5c870ffd937..b0df644f1bd 100644 --- a/homeassistant/components/deconz/sensor.py +++ b/homeassistant/components/deconz/sensor.py @@ -3,10 +3,10 @@ from __future__ import annotations 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 @@ from pydeconz.sensor import ( Pressure, Switch, Temperature, - Thermostat, Time, ) @@ -48,22 +47,21 @@ from homeassistant.helpers.dispatcher import ( 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 @@ ATTR_EVENT_ID = "event_id" 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 @@ async def async_setup_entry( 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 @@ class DeconzSensor(DeconzDevice, SensorEntity): 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 3febbae510b..a21d981900c 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 76babab36be..1d3c4f7a811 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 15e63a6a81f..4ae8fd32e45 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 ee66a159c18..bd51bab44e4 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_DATA = [ + ( # Air quality sensor + { + "config": { + "on": True, + "reachable": True, + }, + "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", + }, + "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", + }, + "websocket_event": {"state": {"airquality": "excellent"}}, + "next_state": "excellent", + }, + ), + ( # Air quality PPB sensor + { + "config": { + "on": True, + "reachable": True, + }, + "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", + }, + "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", + }, + "websocket_event": {"state": {"airqualityppb": 1000}}, + "next_state": "1000", + }, + ), + ( # Battery sensor + { + "config": { + "alert": "none", + "on": True, + "reachable": True, + }, + "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", + }, + ), +] + + +@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.""" - 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", - }, - "2": { - "name": "Presence sensor", - "type": "ZHAPresence", - "state": {"presence": False}, - "config": {}, - "uniqueid": "00:00:00:00:00:00:00:01-00", - }, - "3": { - "name": "Switch 1", - "type": "ZHASwitch", - "state": {"buttonevent": 1000}, - "config": {}, - "uniqueid": "00:00:00:00:00:00:00:02-00", - }, - "4": { - "name": "Switch 2", - "type": "ZHASwitch", - "state": {"buttonevent": 1000}, - "config": {"battery": 100}, - "uniqueid": "00:00:00:00:00:00:00:03-00", - }, - "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", - }, - "6": { - "name": "Consumption sensor", - "type": "ZHAConsumption", - "state": {"consumption": 2, "power": 6}, - "config": {"reachable": True}, - "uniqueid": "00:00:00:00:00:00:00:06-00", - }, - "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", - }, - } - } - - with patch.dict(DECONZ_WEB_REQUEST, data): - config_entry = await setup_deconz_integration(hass, aioclient_mock) - - assert len(hass.states.async_all()) == 6 - 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" + 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} + ) + + # 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"] + + # 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"] + + # Verify entity registry assert ( - light_level_sensor.attributes[ATTR_DEVICE_CLASS] - == SensorDeviceClass.ILLUMINANCE + ent_reg.async_get(expected["entity_id"]).entity_category + is expected["entity_category"] ) - assert light_level_sensor.attributes[ATTR_DAYLIGHT] == 6955 + 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"] - light_level_temp = hass.states.get("sensor.light_level_sensor_temperature") - assert light_level_temp.state == "0.1" + # Verify device registry assert ( - light_level_temp.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.TEMPERATURE + len(dr.async_entries_for_config_entry(dev_reg, config_entry.entry_id)) + == expected["device_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") + # Change state - switch_2_battery_level = hass.states.get("sensor.switch_2_battery_level") - assert switch_2_battery_level.state == "100" - assert ( - switch_2_battery_level.attributes[ATTR_DEVICE_CLASS] - == SensorDeviceClass.BATTERY - ) - assert ( - ent_reg.async_get("sensor.switch_2_battery_level").entity_category - == EntityCategory.DIAGNOSTIC - ) - - 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}, - } + 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.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}, - } - 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 46a0ac81d90..6c97fc99169 100644 --- a/homeassistant/components/iotawatt/coordinator.py +++ b/homeassistant/components/iotawatt/coordinator.py @@ -10,12 +10,16 @@ from homeassistant.config_entries import ConfigEntry 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 @@ class IotawattUpdater(DataUpdateCoordinator): 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 40c176c5f82..02b31a3aa99 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 e8b084a01ab..41137e1a32c 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 @@ class DiscordNotificationService(BaseNotificationService): 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 @@ class DiscordNotificationService(BaseNotificationService): 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 @@ class DiscordNotificationService(BaseNotificationService): 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 @@ class DiscordNotificationService(BaseNotificationService): 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 29248f86cbc..b8a86347aa1 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 c30a7382f27..af47f101fbb 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 9216993eb53..8dba52ebe9d 100644 --- a/homeassistant/util/logging.py +++ b/homeassistant/util/logging.py @@ -33,6 +33,15 @@ class HideSensitiveDataFilter(logging.Filter): 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 e66a8f3a554..1e0d5a322fb 100644 --- a/homeassistant/components/vizio/__init__.py +++ b/homeassistant/components/vizio/__init__.py @@ -9,7 +9,7 @@ from pyvizio.const import APPS 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 @@ _LOGGER = logging.getLogger(__name__) 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 9cca89f77aa..019d016eb2a 100644 --- a/homeassistant/components/vizio/config_flow.py +++ b/homeassistant/components/vizio/config_flow.py @@ -12,7 +12,7 @@ import voluptuous as vol 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 @@ class VizioOptionsConfigFlow(config_entries.OptionsFlow): } ) - 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 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 664a8ae7da8..a076a995f7b 100644 --- a/homeassistant/components/vizio/media_player.py +++ b/homeassistant/components/vizio/media_player.py @@ -10,9 +10,8 @@ from pyvizio.api.apps import find_app_name 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 @@ class VizioDevice(MediaPlayerEntity): 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 @@ class VizioDevice(MediaPlayerEntity): 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 9dbe46ab989..e39f55423d4 100644 --- a/homeassistant/components/econet/sensor.py +++ b/homeassistant/components/econet/sensor.py @@ -13,9 +13,9 @@ from .const import DOMAIN, EQUIPMENT 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 @@ RUNNING_STATE = "running_state" 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_ATTRIBUTES = { 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 a71a93b970e..aaae49afb64 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 b8a86347aa1..746011521e8 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 f5a2fbf7a62..815d290118a 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 1ede9c62e16..cdff1bac634 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 364a3d0db6a..afd8c0fb93e 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 1d60206f2d8..0de303455b9 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 @@ class MjpegCamera(Camera): 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 @@ class MjpegCamera(Camera): 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 @@ class MjpegCamera(Camera): 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 @@ class MjpegCamera(Camera): 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 8372a98d332..18d951cbe30 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 b59986fbe64..aa64e660a83 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 00000000000..ba3d03d5702 --- /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 318a4387510..b6c20bf7542 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 474d1f08b80..bad90efee8f 100644 --- a/homeassistant/components/agent_dvr/camera.py +++ b/homeassistant/components/agent_dvr/camera.py @@ -5,13 +5,8 @@ import logging 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 bb8b072fb06..c52aac37a02 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 @@ class AxisCamera(AxisEntityBase, MjpegCamera): """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 0de303455b9..3a9c1a6cf91 100644 --- a/homeassistant/components/mjpeg/camera.py +++ b/homeassistant/components/mjpeg/camera.py @@ -49,7 +49,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( [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 b9b7f6d42c8..acf170472da 100644 --- a/homeassistant/components/motioneye/camera.py +++ b/homeassistant/components/motioneye/camera.py @@ -27,7 +27,6 @@ import voluptuous as vol 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 @@ async def async_setup_entry( class MotionEyeMjpegCamera(MotionEyeEntity, MjpegCamera): """motionEye mjpeg camera.""" + _name: str + def __init__( self, config_entry_id: str, @@ -173,10 +174,8 @@ class MotionEyeMjpegCamera(MotionEyeEntity, MjpegCamera): ) 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 @@ class MotionEyeMjpegCamera(MotionEyeEntity, MjpegCamera): 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 @@ class MotionEyeMjpegCamera(MotionEyeEntity, MjpegCamera): 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 70e9548414e..5e262ff6903 100644 --- a/homeassistant/components/zoneminder/camera.py +++ b/homeassistant/components/zoneminder/camera.py @@ -3,13 +3,7 @@ from __future__ import annotations 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 6a9e3767065..de277e61510 100644 --- a/homeassistant/components/tuya/cover.py +++ b/homeassistant/components/tuya/cover.py @@ -158,7 +158,10 @@ async def async_setup_entry( 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 4743c5f0401..0a2a17db200 100644 --- a/homeassistant/components/sia/alarm_control_panel.py +++ b/homeassistant/components/sia/alarm_control_panel.py @@ -12,7 +12,6 @@ from homeassistant.components.alarm_control_panel import ( ) 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.core import HomeAssistant, State 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 @@ class SIAAlarmControlPanel(SIABaseEntity, AlarmControlPanelEntity): 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 e26e26cc0b7..f23bd2885a6 100644 --- a/homeassistant/components/sia/binary_sensor.py +++ b/homeassistant/components/sia/binary_sensor.py @@ -13,23 +13,20 @@ from homeassistant.components.binary_sensor import ( 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 @@ ENTITY_DESCRIPTION_MOISTURE = 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 @@ class SIABinarySensor(SIABaseEntity, BinarySensorEntity): 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 537c106fefa..82783611e07 100644 --- a/homeassistant/components/sia/const.py +++ b/homeassistant/components/sia/const.py @@ -24,18 +24,14 @@ CONF_IGNORE_TIMESTAMPS: Final = "ignore_timestamps" 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 311728ad578..8627dea28bc 100644 --- a/homeassistant/components/sia/sia_entity_base.py +++ b/homeassistant/components/sia/sia_entity_base.py @@ -7,6 +7,8 @@ import logging 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.event import async_call_later 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 @@ class SIABaseEntity(RestoreEntity): ) 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 @@ class SIABaseEntity(RestoreEntity): 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 9150099656c..cf52122a499 100644 --- a/homeassistant/components/sia/utils.py +++ b/homeassistant/components/sia/utils.py @@ -8,11 +8,41 @@ from pysiaalarm import SIAEvent 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 00000000000..0e60b1b856c --- /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 8f1005961e8..9ee556c6b7f 100644 --- a/homeassistant/components/version/const.py +++ b/homeassistant/components/version/const.py @@ -11,7 +11,7 @@ from homeassistant.const import CONF_NAME, Platform 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 00000000000..1dcdc23fa9f --- /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 8b09d893afd..f0583a19068 100644 --- a/homeassistant/components/version/sensor.py +++ b/homeassistant/components/version/sensor.py @@ -15,11 +15,8 @@ from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry 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 @@ from .const import ( 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 17d72d6de72..b210a8600b8 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 00000000000..c9551ad4415 --- /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 39e255bb715..3039d5153e5 100644 --- a/homeassistant/components/unifiprotect/config_flow.py +++ b/homeassistant/components/unifiprotect/config_flow.py @@ -82,10 +82,10 @@ class ProtectFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 4613aee954d..537e2fa1121 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 68d90ff82eb..4c7f12a69fa 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 dd247c4cab9..ff67f77257b 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.const import ( 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 @@ MESSAGE_TYPE = "message-type" 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 @@ class WatcherBase: ) 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 @@ class WatcherBase: 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 2fbefe9bbca..a10a2334c73 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 7217fd5940b..c02fa18eefb 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 @@ class Integration: 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 c746c64e46f..1aca6a1f68d 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 @@ BASE = """ 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 55cfd44bae9..d146621b416 100644 --- a/script/hassfest/manifest.py +++ b/script/hassfest/manifest.py @@ -218,6 +218,7 @@ MANIFEST_SCHEMA = vol.Schema( 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 0956230d787..3650ed32987 100644 --- a/tests/components/dhcp/test_init.py +++ b/tests/components/dhcp/test_init.py @@ -25,10 +25,11 @@ from homeassistant.const import ( 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 8cc923840c2..9f2aaff58b7 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 4d4738c39f9..20f2e9dc171 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 a10a2334c73..d49ab1b2891 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -98,6 +98,7 @@ DHCP: list[dict[str, str | bool]] = [ '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 d0543eefd5e..40d04b9bf71 100644 --- a/homeassistant/components/overkiz/number.py +++ b/homeassistant/components/overkiz/number.py @@ -48,6 +48,34 @@ NUMBER_DESCRIPTIONS: list[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 f1e48d83469..c097b04d4eb 100644 --- a/homeassistant/components/overkiz/select.py +++ b/homeassistant/components/overkiz/select.py @@ -73,6 +73,17 @@ SELECT_DESCRIPTIONS: list[OverkizSelectDescription] = [ 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 1e5eb088182..10de6f699dd 100644 --- a/homeassistant/components/overkiz/sensor.py +++ b/homeassistant/components/overkiz/sensor.py @@ -359,6 +359,13 @@ SENSOR_DESCRIPTIONS: list[OverkizSensorDescription] = [ 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 41580aa39d0..c07db62a04f 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 d49ab1b2891..a4dd8b092c8 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -11,6 +11,7 @@ DHCP: list[dict[str, str | bool]] = [ {'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 ff7cd429ea5..cbf83a90963 100644 --- a/homeassistant/components/overkiz/coordinator.py +++ b/homeassistant/components/overkiz/coordinator.py @@ -9,8 +9,10 @@ from pyoverkiz.client import OverkizClient 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 @@ class OverkizDataUpdateCoordinator(DataUpdateCoordinator[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 1c08d47622d..7ad11809e6a 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 746011521e8..ab13c9020b2 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 815d290118a..e2d7f2ec6f3 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 2f8dcc18921..f788d12747d 100644 --- a/homeassistant/components/overkiz/config_flow.py +++ b/homeassistant/components/overkiz/config_flow.py @@ -19,7 +19,7 @@ from homeassistant.components import dhcp, zeroconf 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 - ) + ) as client: + await client.login() - 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 1292310f134..9a317726768 100644 --- a/homeassistant/components/philips_js/__init__.py +++ b/homeassistant/components/philips_js/__init__.py @@ -148,9 +148,12 @@ class PhilipsTVDataUpdateCoordinator(DataUpdateCoordinator[None]): @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 89f13ffadbf..29abbe5dd71 100644 --- a/homeassistant/components/philips_js/config_flow.py +++ b/homeassistant/components/philips_js/config_flow.py @@ -122,9 +122,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 2bd454fbe11..d7a9ed29948 100644 --- a/homeassistant/components/nest/camera_sdm.py +++ b/homeassistant/components/nest/camera_sdm.py @@ -123,6 +123,15 @@ class NestCamera(Camera): 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 fcd4ecc3adc..bfbe63cf02e 100644 --- a/homeassistant/components/flux_led/button.py +++ b/homeassistant/components/flux_led/button.py @@ -63,7 +63,7 @@ class FluxButton(FluxBaseEntity, ButtonEntity): """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 c6f14e929d6..9b1cda2dea1 100644 --- a/homeassistant/components/flux_led/config_flow.py +++ b/homeassistant/components/flux_led/config_flow.py @@ -17,7 +17,7 @@ import voluptuous as vol 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """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 0f65c7c1797..c30418fa8e4 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 5946ab817de..da92931d1e6 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 4534c45e228..0d179cd2b77 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 d7fad9cf0e6..b4e6e87a829 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 3b78baa782b..7edf0ef50f8 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 18d1aac5506..a4266e55fa8 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 ee004fc2250..e8c34f12b11 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 @@ class FluxRemoteAccessSwitch(FluxBaseEntity, SwitchEntity): ) -> 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 dcab5cc01ad..0ff8180a761 100644 --- a/tests/components/flux_led/test_config_flow.py +++ b/tests/components/flux_led/test_config_flow.py @@ -23,7 +23,7 @@ from homeassistant.components.flux_led.const import ( 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 de655c2e6ad..489f6c932c2 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 @@ async def test_config_entry_fills_unique_id_with_directed_discovery( 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 e333691d20c..7ee19346958 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 a4dd8b092c8..687eea81dcf 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -151,6 +151,7 @@ DHCP: list[dict[str, str | bool]] = [ {'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 c8ebdc415ec..6548c71171c 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 687eea81dcf..b876797ee40 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -24,6 +24,7 @@ DHCP: list[dict[str, str | bool]] = [ {'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 00d87bf9837..c0ec18157b6 100644 --- a/homeassistant/components/steamist/config_flow.py +++ b/homeassistant/components/steamist/config_flow.py @@ -48,10 +48,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) 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 2ecd2a1d681..773e56d6612 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 7876272368a..ed887bb6049 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 19a3cf88473..2453958b3de 100644 --- a/homeassistant/components/elkm1/config_flow.py +++ b/homeassistant/components/elkm1/config_flow.py @@ -126,10 +126,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) 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 10d9f7b6e40..7055f3958e9 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 76db04944b5..d8a0feea670 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 97783854614..6acb8c7fca5 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 ab13c9020b2..9be38d29cc5 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 e2d7f2ec6f3..9f90d498f35 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 151a251c8a2..6e2f10c1b36 100644 --- a/homeassistant/components/senseme/config_flow.py +++ b/homeassistant/components/senseme/config_flow.py @@ -42,10 +42,10 @@ class SensemeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 85674f069e1..624b18a8761 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 93a42d1e8ab..e85845dcace 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 9212c7d4247..9f8043ebd85 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 024d61ad8d4..2f96095a97c 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 00000000000..3ae3e819b7e --- /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 fff34120dcf..7d0f716a1c7 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 e4dc05db74a..3513cc855dd 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 6fae2369fd3..997a547e34f 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 26cc502643a..147d0f2e864 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 00000000000..51d7e3a7308 --- /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 a1f7f650821..c704dd81b7f 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 93e4bfa4262..073676503eb 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 c90ecfda925..0b08d9bbc69 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 3f3bbcac514..dde9b024ecd 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 610a5a4f586..7255fe17794 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 0fdcc207646..6688159ee93 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 b439c443e12..430cd76e408 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 6e3d2669c08..72fc4ad7cd3 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 69e1d1f1327..fe92f1e0da8 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 995c16ed994..e77c2da80bd 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 9cf028f4c83..bfbd1c2d210 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 728fd2c3db7..c640d7e0411 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 00000000000..a0010b4e4a8 --- /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 a80a1248eb5..92cea7966a3 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 d2990459f4f..2deebf72442 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 3148940ef05..ae089710c8a 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 12f69b931c8..16e0c74c206 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 907bb2b0d76..768d7dce018 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 0012898808c..6097753fb1f 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 822a75ad7c7..64aae4a89f2 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 bb8a94df0cb..212db9b3f2f 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 8e1e8eae41d..2185a71ed75 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 ae27270b52f..57300096d97 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 46d31db307a..6e95bc0cb36 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 3cdd4500930..792d5297a90 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 00000000000..9cac0f2bb8e --- /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 4ac528bc7ef..c6de5f984fe 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 c467239ea82..caa16f5f8a5 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 50724b72c58..1c6e2fd9809 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 3e8ec8ece2a..9a0c110347f 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 9fad6c40d94..47ae0dc726a 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 136c083b7fb..6dd1e8d2834 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 18ef9d6890a..57a48c4bb80 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 301be92c3b1..1c271c60797 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 43fdcaa4764..774739eea7b 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 aa0a9b03737..ffca8cab545 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 86343321e18..5f448a2e529 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 21db3ef18e0..d02ad4396d0 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 62d94fbb68b..cac360f47a8 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 4b2d98674c5..662258abf46 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 dc8a810dbe3..2ff8f1a2c5a 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 7afcd76e1b3..d16fc316b41 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 fe012d53d8a..6310540cb39 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 b53f8bd127d..6baa206871d 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 00000000000..2f9c5d4ac20 --- /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 06ba7d17aca..907a98fe73a 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 34e61e0a1ae..3277ea6fdad 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 18c2f0869bd..bab52704f79 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 00d9a264883..595e17d1698 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 dab904f0160..b61660d41c4 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 b1115d5bf2c..618e1c4e0d2 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 765a9f5d3b6..7f6732b282e 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 a7fb32ce7a0..bb29747330e 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 b1115d5bf2c..2ac7bbe8ac4 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 3fd7a86e99d..ad89d55c511 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 218bebc5a98..23e4ff4eabd 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 4049e41ea24..c260b24ceeb 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 09104143492..60385881b19 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 23841f83259..1d75dd9ed3f 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 148e4b91c01..c95ea1f5082 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 aec142c37cf..c204f5a9012 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 b7ab8eebbaf..2cdf8ef4863 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 277046d209d..8808b02f5b8 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 e59aa3a3be5..f50730ff9a4 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 d7732fb29f9..e9b7350a2fe 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 18c2f0869bd..bab52704f79 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 99d75f26cc0..c76b5ba7252 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 c6929ecbbf2..2646eb1883e 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 f08f75f2da9..57714e8ee95 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 13afb78596f..fa290b1bfdc 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 aa6c808eec7..d927982617c 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 26ad68c7d5b..d49ed2e70d5 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 98eaae46a3f..929bcbbc248 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 18c2f0869bd..bab52704f79 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 2fa90c23ca8..f7a241533da 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 5891bb1e3dd..82813ee3a53 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 1bafcdf8e44..d998a1a9d74 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 15d48e9c63c..d6b6c51e6f5 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 0304a1423f6..2a94c2fa87d 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 29ffda9a5e9..37b91a3dce8 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 447231bcb0c..3abe8e270af 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 a044d731d17..7750317d34d 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 00000000000..d15111e97b0 --- /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 28c82949398..57a99233d13 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 50851e08956..f820e4ef03e 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 110dc2a5c05..de36fc19681 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 4bc75878b2b..f8b99b0c09b 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 4cf45560e8e..5b06a6c3d01 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 24b945708d3..e0ad2d03cff 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 cc054a26d97..e1e7e8ff079 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 b507bda970d..cdd1dc4b215 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 c2a702277fe..ae781d86ca8 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 1bb004b5464..ff089be667f 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 59a86b9dcef..3ad2637a5d2 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 bfd24267091..87e5fd2cd8d 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 24e5bde93c5..af900a992a6 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 d398d589513..7723b5bbdb9 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 cbe07cce0f7..db3aa9c03d3 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 80d36689553..6d28f2d1842 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 18c2f0869bd..bab52704f79 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 ae7c5ea3f30..5b17c464742 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 dedd4eb3def..99331948120 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 00000000000..84524b20786 --- /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 e63383e4d7c..ac63fdd0fbf 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 b81293d60a6..58a68ae1aa4 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 00000000000..6dda6c14145 --- /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 d1981d80c7e..c56dbc9bc45 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 292ae6a8539..83c0b75f9d3 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 206fbdab2a2..32c00287a34 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 45247108dc6..82588fd8951 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 00000000000..5561ddd5d2a --- /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 8b1e7f9d282..f6af3b500f2 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 d3263b9c849..eba13432262 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 a24f4954f97..cd3d44f62e4 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 00000000000..eea9a1e042d --- /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 4fb06d067dc..e7aea4fbd89 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 23a73311f71..e7e994b2a56 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 50eb1dfd177..54e03b32b8a 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 4ac4f3e8445..0c83d2de637 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 2efdf57dbcb..490ed36838d 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 cedcd1691cf..fe1e0e9a73d 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 5cec565d287..e8d227cdedc 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 00000000000..35d9add569f --- /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 06c6565c719..6a6f656db76 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 3ba3fdb4330..c749d7f7def 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 742ad113bb7..929c1cdde06 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 143386f4703..ef7089357ab 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 bf55da881ce..667512e722b 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 00000000000..a3ea32fadc3 --- /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 35980b52f40..61fd1656442 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 cb962803136..e2fa45d9190 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 7e626db563b..9608dfe0cc1 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 7f7e163cfbb..bee37751f76 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 deb451383f6..254bd2db775 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 1df0b2de9c3..749746bd52a 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 18c2f0869bd..bab52704f79 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 15d2736c269..b24b965d004 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 a675c021cd3..abc00e150fb 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 274b4b7d11a..555cbf3406d 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 03e8b3f1513..9d5c561fb7a 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 35f0bd1867e..1e427f465db 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 b61c256ef18..180acc83776 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 15ba157f55c..0e2e754f90c 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 fc1a4535178..498610b5896 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 e69a3c00eed..a77dd8851fe 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 654095e2cfd..1cb4117e799 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 00000000000..4c86485bcc2 --- /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 a8efc9d1e95..5087afff1bd 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 a6782ec138a..e332f005aa5 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 ed87fbbdf78..c99e65e247c 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 00000000000..cee9047f155 --- /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 5105f19e2bc..594bd343ae1 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 9f3915dd39b..e8036c15205 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 00000000000..da8048f13fb --- /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 22e45ca54fa..e055c1c1eb9 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 7d43245e617..a5facf43b79 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 a5bd6ebdebe..b40afa3ffcd 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 376a62b9ff0..8680df5adc5 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 e1fd6af43bc..85b0863bd7e 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 00000000000..676af9e30aa --- /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 18c2f0869bd..bab52704f79 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 00000000000..b6f2cec8396 --- /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 2c244b1bfc0..60ca2a8a884 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 ad8951c92c5..73b933fefcf 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 3741ef6a8f3..a2516964840 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 97bb7fc25ba..a612ea165a4 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 f398c1a49bd..9cb84a0e7bd 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 d26658e666a..7c99571a9ae 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 3b0781bbc73..ebd093c22f7 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 00000000000..dcf6c75a653 --- /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 bf6563a3f48..7f2090b5b71 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 60de0710e40..30d1455c470 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 7d5485e354e..e0a95b88daf 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 bef8eae0d32..dd422f29fea 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 ce088267106..b677427996f 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 2af575a32a1..463745b6bbf 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 00000000000..17d5aa4c832 --- /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 9b1cda2dea1..ee8da5bd66a 100644 --- a/homeassistant/components/flux_led/config_flow.py +++ b/homeassistant/components/flux_led/config_flow.py @@ -81,10 +81,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) 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 c30418fa8e4..cd0d9424ead 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 0ff8180a761..b858f6d995a 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 2856fe94240..e3095ada47a 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 b876797ee40..f78c8003457 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -87,6 +87,7 @@ DHCP: list[dict[str, str | bool]] = [ {'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 29b260c2fa3..4e1ece9a3b0 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 f78c8003457..26b2aa61aeb 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -41,6 +41,7 @@ DHCP: list[dict[str, str | bool]] = [ {'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 9e2a9363eff..97a73434b26 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 26b2aa61aeb..a92e9b4dd92 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -77,6 +77,7 @@ DHCP: list[dict[str, str | bool]] = [ {'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 016ade188f4..98129e24f01 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 a92e9b4dd92..1cf62e9375c 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -73,6 +73,7 @@ DHCP: list[dict[str, str | bool]] = [ {'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 23a2f07b970..fe0fa9720ca 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 1cf62e9375c..ead49a36b67 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -45,6 +45,7 @@ DHCP: list[dict[str, str | bool]] = [ {'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 231c00e3d24..9039018da6e 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 3d1967ecff3..8e894b4e295 100644 --- a/homeassistant/components/hue/scene.py +++ b/homeassistant/components/hue/scene.py @@ -5,7 +5,11 @@ from typing import Any 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 @@ class HueSceneEntity(HueBaseEntity, SceneEntity): 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 7f8e048d692..7fb40cba38f 100644 --- a/homeassistant/components/hue/switch.py +++ b/homeassistant/components/hue/switch.py @@ -5,8 +5,12 @@ from typing import Any, Union 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 @@ from .v2.entity import HueBaseEntity 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 47617b45af6..a7077ccf765 100644 --- a/homeassistant/components/hue/v2/binary_sensor.py +++ b/homeassistant/components/hue/v2/binary_sensor.py @@ -4,13 +4,13 @@ from __future__ import annotations from typing import Any, Union from aiohue.v2 import HueBridgeV2 -from aiohue.v2.controllers.config import EntertainmentConfigurationController +from aiohue.v2.controllers.config import ( + EntertainmentConfiguration, + EntertainmentConfigurationController, +) from aiohue.v2.controllers.events import EventType from aiohue.v2.controllers.sensors import MotionController -from aiohue.v2.models.entertainment import ( - EntertainmentConfiguration, - EntertainmentStatus, -) +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 @@ class HueEntertainmentActiveSensor(HueBinarySensorBase): 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 c8c2f9e423b..721425606bc 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 homeassistant.helpers.entity_registry import async_get as async_get_entity_ 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 @@ class HueBaseEntity(Entity): self, bridge: HueBridge, controller: BaseResourcesController, - resource: CLIPResource, + resource: HueResource, ) -> None: """Initialize a generic Hue resource entity.""" self.bridge = bridge @@ -122,7 +133,7 @@ class HueBaseEntity(Entity): # 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 300f08727ba..31c5a502853 100644 --- a/homeassistant/components/hue/v2/group.py +++ b/homeassistant/components/hue/v2/group.py @@ -7,7 +7,7 @@ from typing import Any 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 @@ class GroupedHueLight(HueBaseEntity, LightEntity): 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 ff2b7b78e7d..d331393d29b 100644 --- a/homeassistant/components/hue/v2/sensor.py +++ b/homeassistant/components/hue/v2/sensor.py @@ -12,10 +12,10 @@ from aiohue.v2.controllers.sensors import ( 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 9be38d29cc5..e49ada0b8ce 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 9f90d498f35..5b9a5db5f7b 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 9e9ed9af31b..d0d15d320e0 100644 --- a/tests/components/hue/conftest.py +++ b/tests/components/hue/conftest.py @@ -8,7 +8,6 @@ from unittest.mock import AsyncMock, Mock, patch 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 @@ def create_mock_api_v2(hass): 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 c7578df3a49..f0265233e4e 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 257f1a253c3..e8086709705 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 ec8538e2b36..8bb151199d7 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 aa64e660a83..32cb61a4921 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 89768221665..4ad1622c6ca 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 05c51fdf591..f14a0f71bf1 100644 --- a/tests/components/samsungtv/conftest.py +++ b/tests/components/samsungtv/conftest.py @@ -5,19 +5,21 @@ import pytest import homeassistant.util.dt as dt_util -RESULT_ALREADY_CONFIGURED = "already_configured" -RESULT_ALREADY_IN_PROGRESS = "already_in_progress" + +@pytest.fixture(autouse=True) +def fake_host_fixture() -> None: + """Patch gethostbyname.""" + with 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, patch( - "homeassistant.components.samsungtv.config_flow.socket.gethostbyname", - return_value="fake_host", - ): + 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 199c7d87eed..57bb99354dc 100644 --- a/tests/components/samsungtv/test_config_flow.py +++ b/tests/components/samsungtv/test_config_flow.py @@ -43,10 +43,9 @@ from homeassistant.core import HomeAssistant 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 ba3d03d5702..990c25c8f3e 100644 --- a/tests/components/samsungtv/test_diagnostics.py +++ b/tests/components/samsungtv/test_diagnostics.py @@ -7,9 +7,10 @@ from homeassistant.components.samsungtv import DOMAIN 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 bd3e2a51256..e49b8fdc5ee 100644 --- a/tests/components/samsungtv/test_init.py +++ b/tests/components/samsungtv/test_init.py @@ -55,27 +55,21 @@ REMOTE_CALL = { 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 dca7e4366f3..2d8c07c22cf 100644 --- a/tests/components/samsungtv/test_media_player.py +++ b/tests/components/samsungtv/test_media_player.py @@ -117,6 +117,9 @@ MOCK_CONFIG_NOTURNON = { ] } +# 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 test_play_media(hass, remote): 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 04bd538322e..5cb00c11191 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 ead49a36b67..b4536f92426 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -36,6 +36,7 @@ DHCP: list[dict[str, str | bool]] = [ {'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 63f09e3dfb3..db1601edd67 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 b4536f92426..d895468d559 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -18,6 +18,7 @@ DHCP: list[dict[str, str | bool]] = [ {'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 e6b4c4aceab..83f4b820523 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 5cb351989be..8b05f90041a 100644 --- a/homeassistant/components/tplink/config_flow.py +++ b/homeassistant/components/tplink/config_flow.py @@ -35,10 +35,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 fdc9fcea83e..a3792238fb2 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 67c4302e6de..6680466ecf0 100644 --- a/homeassistant/components/ezviz/camera.py +++ b/homeassistant/components/ezviz/camera.py @@ -10,9 +10,9 @@ from homeassistant.components import ffmpeg 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 a5a3444c0dd..780d06383f9 100644 --- a/homeassistant/components/ezviz/config_flow.py +++ b/homeassistant/components/ezviz/config_flow.py @@ -259,7 +259,7 @@ class EzvizConfigFlow(ConfigFlow, domain=DOMAIN): 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 4dffe1d7e25..9a3129b7dd6 100644 --- a/tests/components/ezviz/test_config_flow.py +++ b/tests/components/ezviz/test_config_flow.py @@ -19,7 +19,11 @@ from homeassistant.components.ezviz.const import ( 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 b3448d0b790..9f1d307c14a 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 d895468d559..b0edb218b7b 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -27,6 +27,7 @@ DHCP: list[dict[str, str | bool]] = [ {'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 a0d7208f1b2..6f1b6097c4f 100644 --- a/homeassistant/components/vicare/sensor.py +++ b/homeassistant/components/vicare/sensor.py @@ -189,10 +189,74 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = ( 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 8ab9c0ac73f..8b0dd26fc32 100644 --- a/homeassistant/components/elkm1/__init__.py +++ b/homeassistant/components/elkm1/__init__.py @@ -14,6 +14,7 @@ import voluptuous as vol 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.const import ( 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 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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 @@ class ElkEntity(Entity): """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 @@ class ElkAttachedEntity(ElkEntity): 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 a72a0221907..909bfa3bd02 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 b0edb218b7b..25b82220223 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -23,6 +23,7 @@ DHCP: list[dict[str, str | bool]] = [ {'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 96425b2ea93..cb009efeb07 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -11,7 +11,7 @@ import attr 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 @@ class DeviceRegistry: """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 @@ class DeviceRegistry: 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 @@ class DeviceRegistry: 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 @@ class DeviceRegistry: ("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 a0949bad03c..4e4150fe504 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() -async def test_update_suggested_area(registry, area_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"] == {"hw_version": None} + + +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 9621e18bf17..f6046986158 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 25b82220223..11c1331a72d 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -73,6 +73,7 @@ DHCP: list[dict[str, str | bool]] = [ {'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 32cb61a4921..a419628d50a 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 697157bd866..213d0dabc91 100644 --- a/homeassistant/components/aseko_pool_live/__init__.py +++ b/homeassistant/components/aseko_pool_live/__init__.py @@ -17,7 +17,7 @@ from .const import DOMAIN _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 00000000000..f67ea58bfc4 --- /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 a3354007868..abf08b70dad 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -140,20 +140,13 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): @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 6f9a258bb38..c40ad32c078 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 22764f81080..553b3cc9590 100644 --- a/homeassistant/components/tuya/base.py +++ b/homeassistant/components/tuya/base.py @@ -41,15 +41,15 @@ class IntegerTypeData: @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 @@ class IntegerTypeData: 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 | 120 ++++++------------ .../components/sleepiq/binary_sensor.py | 74 +++++------ .../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 | 77 +++++------ 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 | 63 ++++----- tests/components/sleepiq/test_config_flow.py | 80 ++++++++++++ tests/components/sleepiq/test_init.py | 99 +++++++-------- tests/components/sleepiq/test_sensor.py | 67 ++++------ 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, 739 insertions(+), 464 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 afd8c0fb93e..bd8553359cf 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 ceefad2bad4..c0dd609233a 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 f6ba5a0393f..5a69cfacd11 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.""" +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 - def __init__(self, client): - """Initialize the data object.""" - self._client = client - self.beds = {} + coordinator = SleepIQDataUpdateCoordinator( + hass, + client=client, + username=entry.data[CONF_USERNAME], + ) - self.update() + # Call the SleepIQ API to refresh data + await coordinator.async_config_entry_first_refresh() - @Throttle(MIN_TIME_BETWEEN_UPDATES) - def update(self): - """Get the latest data from SleepIQ.""" - self._client.login() - beds = self._client.beds_with_sleeper_status() + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator - self.beds = {bed.bed_id: bed for bed in beds} + hass.config_entries.async_setup_platforms(entry, PLATFORMS) + + return True -class SleepIQSensor(Entity): - """Implementation of a SleepIQ sensor.""" - - 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 - - # added by subclass - self._name = None - self.type = None - - @property - def name(self): - """Return the name of the sensor.""" - return "SleepNumber {} {} {}".format( - 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 - # 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 f821a569254..890cd6711a6 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() + _attr_device_class = BinarySensorDeviceClass.OCCUPANCY - @property - def is_on(self): - """Return the status of the sensor.""" - return self._state is True + 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) - @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 + @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 00000000000..aff4d7e8dc7 --- /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 64f508167e1..3fc0ae999fd 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 00000000000..467238e907e --- /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 00000000000..350435573f1 --- /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 ac734393197..a516bd7545b 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 523014cf8cf..52ded76762d 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) + 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) - 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 + @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 00000000000..21ceead3d0a --- /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 00000000000..31de29c8690 --- /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 be18826bedb..72037428e68 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -286,6 +286,7 @@ FLOWS = [ "shopping_list", "sia", "simplisafe", + "sleepiq", "sma", "smappee", "smart_meter_texas", diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index 11c1331a72d..8809dd45f4a 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -88,6 +88,7 @@ DHCP: list[dict[str, str | bool]] = [ {'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 18d951cbe30..6187f296ec2 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 00000000000..707fc436c15 --- /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 00000000000..f1e59f5ad2d --- /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 00000000000..5fb12da0507 --- /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 00000000000..1d5c0d89943 --- /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 00000000000..c9b60824115 --- /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 00000000000..a665db7de29 --- /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 00000000000..c009e684220 --- /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 9b4092d9d48..ca9bf3c84fc 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"} +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_sensor_setup(hass, requests_mock): - """Test for successfully setting up the SleepIQ platform.""" - mock_responses(requests_mock) +async def test_binary_sensors(hass, setup_entry): + """Test the SleepIQ binary sensors.""" + entity_registry = er.async_get(hass) - await async_setup_component(hass, "sleepiq", {"sleepiq": CONFIG}) + 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" - device_mock = MagicMock() - sleepiq.setup_platform(hass, CONFIG, device_mock, MagicMock()) - devices = device_mock.call_args[0][0] - assert len(devices) == 2 + entry = entity_registry.async_get("binary_sensor.sleepnumber_ile_test1_is_in_bed") + assert entry + assert entry.unique_id == "-31_Test1_is_in_bed" - left_side = devices[1] - assert left_side.name == "SleepNumber ILE Test1 Is In Bed" - assert left_side.state == "on" + # 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 - right_side = devices[0] - assert right_side.name == "SleepNumber ILE Test2 Is In Bed" - assert right_side.state == "off" + entry = entity_registry.async_get("binary_sensor.sleepnumber_ile_test2_is_in_bed") + assert entry + assert entry.unique_id == "-31_Test2_is_in_bed" - -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" + 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 00000000000..e4a422c888f --- /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 68ca876504f..15af03e14ce 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 +"""Tests for the SleepIQ integration.""" +from unittest.mock import patch -from homeassistant import setup -import homeassistant.components.sleepiq as sleepiq +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 load_fixture - -CONFIG = {"sleepiq": {"username": "foo", "password": "bar"}} +from tests.common import async_fire_time_changed +from tests.components.sleepiq.conftest import ( + mock_bed_family_status, + mock_beds, + mock_sleepers, +) -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_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_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_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_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"), - ) +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 - response = sleepiq.setup(hass, CONFIG) - assert not response + async_fire_time_changed(hass, utcnow() + UPDATE_INTERVAL) + await hass.async_block_till_done() - -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) + 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 7a7e47f03fa..baa8732365b 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"} +from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_ICON +from homeassistant.helpers import entity_registry as er -async def test_setup(hass, requests_mock): - """Test for successfully setting up the SleepIQ platform.""" - mock_responses(requests_mock) +async def test_sensors(hass, setup_entry): + """Test the SleepIQ binary sensors for a bed with two sides.""" + entity_registry = er.async_get(hass) - assert await async_setup_component(hass, "sleepiq", {"sleepiq": CONFIG}) + 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" + ) - device_mock = MagicMock() - sleepiq.setup_platform(hass, CONFIG, device_mock, MagicMock()) - devices = device_mock.call_args[0][0] - assert len(devices) == 2 + entry = entity_registry.async_get("sensor.sleepnumber_ile_test1_sleepnumber") + assert entry + assert entry.unique_id == "-31_Test1_sleep_number" - left_side = devices[1] - left_side.hass = hass - assert left_side.name == "SleepNumber ILE Test1 SleepNumber" - assert left_side.state == 40 + # 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 - right_side = devices[0] - right_side.hass = hass - assert right_side.name == "SleepNumber ILE Test2 SleepNumber" - assert right_side.state == 80 + 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" + ) - -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 + 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 512f36c0e6a..00000000000 --- 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 d03fb6e329f..00000000000 --- 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 08c9569c4dc..00000000000 --- 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 0c93d74d35f..00000000000 --- 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 227609154b5..00000000000 --- 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 fdd8943574f..00000000000 --- 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 4089e1b1d95..00000000000 --- 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 c0dd609233a..e27c9488f0a 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 f6046986158..b4aa3137482 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 d509da91304..54f1cc422a3 100644 --- a/homeassistant/components/samsungtv/bridge.py +++ b/homeassistant/components/samsungtv/bridge.py @@ -303,8 +303,8 @@ class SamsungTVWSBridge(SamsungTVBridge): 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 4b4c6fb7af9..c98dbbd270a 100644 --- a/homeassistant/components/mqtt/abbreviations.py +++ b/homeassistant/components/mqtt/abbreviations.py @@ -50,6 +50,7 @@ ABBREVIATIONS = { "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 07c2898f204..360e726c89e 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 ac11ee554a8..f6e43b29653 100644 --- a/homeassistant/components/netatmo/__init__.py +++ b/homeassistant/components/netatmo/__init__.py @@ -31,10 +31,7 @@ from homeassistant.helpers import ( 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 @@ from .const import ( 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 async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: "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 async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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 async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) return + webhook_register( + hass, + DOMAIN, + "Netatmo", + entry.data[CONF_WEBHOOK_ID], + async_handle_webhook, + ) + 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) - 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 async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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 async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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 ffa68d75011..911fd6c309a 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 517d6b2bb9b..f51266e45dc 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 b2201196917..a348299e282 100644 --- a/homeassistant/components/google_assistant/helpers.py +++ b/homeassistant/components/google_assistant/helpers.py @@ -4,6 +4,7 @@ from __future__ import annotations 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.entity_registry import RegistryEntry 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 @@ class AbstractConfig(ABC): 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 @@ class AbstractConfig(ABC): """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 @@ class AbstractConfig(ABC): # 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 31fd02544ff..80bc61cc61d 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 07b87632f19..e06344827a7 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 ff441e44f25..dc29e5df4ab 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 12241a00513..b4def6fb379 100644 --- a/homeassistant/components/tuya/climate.py +++ b/homeassistant/components/tuya/climate.py @@ -167,8 +167,12 @@ class TuyaClimateEntity(TuyaEntity, ClimateEntity): 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 d976bee2792..ee653534fc8 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 2398a6203ad..43c3998849c 100644 --- a/homeassistant/components/tts/media_source.py +++ b/homeassistant/components/tts/media_source.py @@ -70,8 +70,8 @@ class TTSMediaSource(MediaSource): ) -> 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 @@ class TTSMediaSource(MediaSource): ) @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 @@ class TTSMediaSource(MediaSource): 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 3bfd204a228..22edfef5358 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 | 96 +++++++++++---- tests/components/deconz/test_services.py | 4 +- 13 files changed, 366 insertions(+), 69 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 00000000000..2ad53d8ad63 --- /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 5f6d77a69fd..ca2e791f9e9 100644 --- a/homeassistant/components/deconz/const.py +++ b/homeassistant/components/deconz/const.py @@ -25,6 +25,7 @@ CONF_MASTER_GATEWAY = "master" 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 bbd4051c177..45f57729a6f 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 @@ class DeconzDevice(DeconzBase, Entity): 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 5330fdb3226..e3cf6442079 100644 --- a/homeassistant/components/deconz/light.py +++ b/homeassistant/components/deconz/light.py @@ -5,7 +5,7 @@ from __future__ import annotations 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 6fb6bbce87a..bbbafffed7a 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 9fcccc52386..c188d7faffa 100644 --- a/homeassistant/components/deconz/scene.py +++ b/homeassistant/components/deconz/scene.py @@ -5,7 +5,7 @@ from __future__ import annotations 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.core import HomeAssistant, callback 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 @@ async def async_setup_entry( | 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 @@ async def async_setup_entry( 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 e49ada0b8ce..34598e82c69 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 5b9a5db5f7b..aa830cb97c9 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 00000000000..804a93d5ea4 --- /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 17da9f1141a..d0905f5ba5f 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 30473814f26..8a449456fde 100644 --- a/tests/components/deconz/test_gateway.py +++ b/tests/components/deconz/test_gateway.py @@ -12,6 +12,7 @@ from homeassistant.components.alarm_control_panel import ( 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 e6f74cd0529..f28e83d3f39 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 service calls + # Verify state data - mock_deconz_put_request( - aioclient_mock, config_entry.data, "/groups/1/scenes/1/recall" + scene = hass.states.get(expected["entity_id"]) + assert scene.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"] ) - # 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] == {} - await hass.config_entries.async_unload(config_entry.entry_id) + # 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 5cdd36440ea..086da5e24c4 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 7ee19346958..7794162d32c 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 34598e82c69..37d68deecfc 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 aa830cb97c9..30166e622ea 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 a0017c36684..400cea3f78c 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 @@ class ConfigFlow(data_entry_flow.FlowHandler): 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 cad92a5d92d..b62e9bffbce 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 9039018da6e..d82359613f7 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 37d68deecfc..b4df475312e 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 30166e622ea..0bea264aa25 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 a8d90d87ac3..68e12887daf 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 b4df475312e..c98fb222b5f 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 56f49307662..11b122e2b5a 100644 --- a/homeassistant/components/cloud/alexa_config.py +++ b/homeassistant/components/cloud/alexa_config.py @@ -187,7 +187,11 @@ class CloudAlexaConfig(alexa_config.AbstractConfig): 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 4ae3b44f1fe..7988a648901 100644 --- a/homeassistant/components/cloud/google_config.py +++ b/homeassistant/components/cloud/google_config.py @@ -181,7 +181,11 @@ class CloudGoogleConfig(AbstractConfig): 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 988a96ce08e..5fe3f7d0012 100644 --- a/homeassistant/components/brunt/__init__.py +++ b/homeassistant/components/brunt/__init__.py @@ -11,7 +11,6 @@ from homeassistant.config_entries import ConfigEntry 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 @@ _LOGGER = logging.getLogger(__name__) 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 166b7cda34c..e3c3f10b98f 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -20,6 +20,7 @@ from homeassistant.const import ( 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 @@ class MqttBinarySensor(MqttEntity, BinarySensorEntity, RestoreEntity): _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 f14a0f71bf1..14f33524f52 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 57bb99354dc..a3e16ba81c3 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 2d8c07c22cf..aab137a9a2c 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 ed3c50cdb00..cbd1f4ffaba 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -1058,6 +1058,8 @@ SERVICE_SCHEMA = vol.All( ), 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 62ae79ec5cc..daa8d11d601 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 8e894b4e295..c21a96e4d9a 100644 --- a/homeassistant/components/hue/scene.py +++ b/homeassistant/components/hue/scene.py @@ -72,7 +72,7 @@ async def async_setup_entry( 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 54f1cc422a3..ed520acc1f7 100644 --- a/homeassistant/components/samsungtv/bridge.py +++ b/homeassistant/components/samsungtv/bridge.py @@ -358,6 +358,13 @@ class SamsungTVWSBridge(SamsungTVBridge): 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 aab137a9a2c..a274e0bbc98 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 a3e16ba81c3..c33424ed45b 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 a274e0bbc98..ed09d9e33e0 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 4f974d2c182..288c6d6e588 100644 --- a/homeassistant/components/input_select/__init__.py +++ b/homeassistant/components/input_select/__init__.py @@ -16,6 +16,7 @@ from homeassistant.const import ( 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_SELECT_LAST = "select_last" 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 @@ UPDATE_FIELDS = { } +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 @@ CONFIG_SCHEMA = vol.Schema( 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 @@ class InputSelect(SelectEntity, RestoreEntity): 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 783c4c2b9e4..7a239bac45b 100644 --- a/tests/components/input_select/test_init.py +++ b/tests/components/input_select/test_init.py @@ -15,6 +15,8 @@ from homeassistant.components.input_select import ( SERVICE_SELECT_OPTION, SERVICE_SELECT_PREVIOUS, SERVICE_SET_OPTIONS, + STORAGE_VERSION, + STORAGE_VERSION_MINOR, ) from homeassistant.const import ( ATTR_EDITABLE, @@ -25,7 +27,7 @@ from homeassistant.const import ( 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 @@ from tests.common import mock_restore_cache 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 @@ def storage_setup(hass, hass_storage): 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 6acb8c7fca5..b7db3880a5e 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 c98fb222b5f..be3a0db1d4b 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 0bea264aa25..e35ee84bb85 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 af47f101fbb..c30a7382f27 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 c33424ed45b..690e47ea816 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 e3c3f10b98f..40d5c876c11 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -126,6 +126,9 @@ class MqttBinarySensor(MqttEntity, BinarySensorEntity, RestoreEntity): 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 @@ class MqttBinarySensor(MqttEntity, BinarySensorEntity, RestoreEntity): 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 8dd53901755..31a784a259e 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -172,6 +172,9 @@ class MqttSensor(MqttEntity, SensorEntity, RestoreEntity): 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 @@ class MqttSensor(MqttEntity, SensorEntity, RestoreEntity): 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 af2969f6f7a..f3a07616d93 100644 --- a/homeassistant/components/aussie_broadband/__init__.py +++ b/homeassistant/components/aussie_broadband/__init__.py @@ -15,7 +15,7 @@ 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 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_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: "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 c18e808e6ad..5eaf39853b5 100644 --- a/homeassistant/components/aussie_broadband/config_flow.py +++ b/homeassistant/components/aussie_broadband/config_flow.py @@ -9,12 +9,10 @@ import voluptuous as vol 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ), 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 04c1cff97b5..09946cef03d 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.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.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 @@ SENSOR_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = ( 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 @@ SENSOR_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = ( 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 @@ class AussieBroadandSensorEntity(CoordinatorEntity, SensorEntity): 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 42ebcbbdbe8..d5b7d2f1fa1 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 a59a297c265..4d18251f270 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 7e919636b09..8a5f6b6763f 100644 --- a/tests/components/aussie_broadband/test_config_flow.py +++ b/tests/components/aussie_broadband/test_config_flow.py @@ -4,8 +4,8 @@ from unittest.mock import patch 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 @@ from homeassistant.data_entry_flow import ( 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 5fa71d73632..5055550be7c 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 4d18251f270..2843916df2e 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 00000000000..19640de6aa7 --- /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 9dbd275d2bc..efe1fca7c80 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 00000000000..2ae994c70c9 --- /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 00000000000..6952329f20e --- /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 2b84b802b6b..f2f4c17af9c 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 eaf987f95e6..ecda52c8c5b 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 3b7cb4a4f56..c4edae6f9ee 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 00000000000..2455bf8695d --- /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 00000000000..be6eaaee436 --- /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 d01479fbd87..e9c8bb02df9 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 00000000000..19f5d1e1587 --- /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 00000000000..91bbd571ab9 --- /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 d0c3523da94..eaea05d0779 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 c533197ca84..c53287164ee 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 8808b02f5b8..64604c1f460 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 1eb2914d110..10e8dfa3d17 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 f1ffc44957e..0dfc063a035 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 00000000000..5eac883adf0 --- /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 00000000000..d15111e97b0 --- /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 7b03a828718..f1702a6d703 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 00000000000..8d0c475586b --- /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 dc27752e935..ce11c538997 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 848f72e62d6..3974c507fd4 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 f668538909b..856ac220d64 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 d6e5cd5904b..6f6ffccd477 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 f14e786aab6..dfa90cb1cc8 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 00000000000..9c37f37a0ef --- /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 00000000000..49f37a267d0 --- /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 00000000000..82754ec0865 --- /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 00000000000..f74355cdc7d --- /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 00000000000..d93bfe0fa68 --- /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 413ab93839a..95a60ab55ea 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 4755e9f5d09..99f7f02771d 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 00000000000..d15f31b514c --- /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 00000000000..0656e47c8be --- /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 00000000000..1e0dbb1f7dd --- /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 dd091bd10eb..8d4e41401c8 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 d4b1fc92ed8..694973f8ffa 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 00000000000..f069ef7b324 --- /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 00000000000..f55937b4ec7 --- /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 cbc5df64496..6f1d811c291 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 be3a0db1d4b..2898404400e 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 e35ee84bb85..ae1bcff2847 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 8a494d193b7..f8df1a4134e 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 2898404400e..1b8e6616d59 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 ae1bcff2847..a21bfb336e5 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 a419628d50a..d5c9f48ee47 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 00000000000..e14600ff52b --- /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 d366ae39961..f2e0263a4e4 100644 --- a/homeassistant/components/netgear/const.py +++ b/homeassistant/components/netgear/const.py @@ -5,7 +5,7 @@ from homeassistant.const import Platform 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 3778c36d81a..9e44495aa62 100644 --- a/homeassistant/components/netgear/router.py +++ b/homeassistant/components/netgear/router.py @@ -209,6 +209,11 @@ class NetgearRouter: 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 5fe3f7d0012..988a96ce08e 100644 --- a/homeassistant/components/brunt/__init__.py +++ b/homeassistant/components/brunt/__init__.py @@ -11,6 +11,7 @@ from homeassistant.config_entries import ConfigEntry 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 @@ _LOGGER = logging.getLogger(__name__) 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 72277a820e4..11bafbca07b 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 1b8e6616d59..ac972d24fb5 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 a21bfb336e5..7a1d16c0e87 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 f347a5187ea..4e68cc2e2dd 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 d4bf58a53ce..ac0cedcffef 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 3f29c3a2e67..8362854a5f6 100644 --- a/tests/components/zwave_js/test_api.py +++ b/tests/components/zwave_js/test_api.py @@ -6,6 +6,7 @@ from unittest.mock import patch 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 90c5eebaeb2..83801d50354 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 3d65f4eb297..0de580ef7b7 100644 --- a/homeassistant/components/google_cloud/tts.py +++ b/homeassistant/components/google_cloud/tts.py @@ -122,13 +122,9 @@ SUPPORTED_OPTIONS = [ 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 @@ class GoogleCloudTTSProvider(Provider): 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 ac972d24fb5..d963262b712 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 c30a7382f27..af47f101fbb 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 18d2325f38c..324a3d1b32f 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 990c25c8f3e..67bc012ced1 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 288c6d6e588..ae5fc9d251e 100644 --- a/homeassistant/components/input_select/__init__.py +++ b/homeassistant/components/input_select/__init__.py @@ -45,15 +45,27 @@ STORAGE_KEY = DOMAIN 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 7a239bac45b..d65140dcbf9 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 not resp["success"] + assert resp["error"]["code"] == "unknown_error" + assert resp["error"]["message"] == "Duplicate options are not allowed" - 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"] + 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 043a291f159..e145edde7d7 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -80,6 +80,7 @@ CONF_ACTION_TOPIC = "action_topic" 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_COMMAND_TOPIC = "fan_mode_command_topic" 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_COMMAND_TOPIC = "power_command_topic" 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 @@ MQTT_CLIMATE_ATTRIBUTES_BLOCKED = frozenset( 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 @@ VALUE_TEMPLATE_KEYS = ( 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 @@ TOPIC_KEYS = ( 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 @@ _PLATFORM_SCHEMA_BASE = SCHEMA_BASE.extend( ): 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 @@ _PLATFORM_SCHEMA_BASE = SCHEMA_BASE.extend( [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 @@ _PLATFORM_SCHEMA_BASE = SCHEMA_BASE.extend( ).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 @@ class MqttClimate(MqttEntity, ClimateEntity): 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 @@ class MqttClimate(MqttEntity, ClimateEntity): 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 @@ class MqttClimate(MqttEntity, ClimateEntity): 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 @@ class MqttClimate(MqttEntity, ClimateEntity): 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 @@ class MqttClimate(MqttEntity, ClimateEntity): 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 @@ class MqttClimate(MqttEntity, ClimateEntity): 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 @@ class MqttClimate(MqttEntity, ClimateEntity): 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 @@ class MqttClimate(MqttEntity, ClimateEntity): # 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 @@ class MqttClimate(MqttEntity, ClimateEntity): 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 @@ class MqttClimate(MqttEntity, ClimateEntity): 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 @@ class MqttClimate(MqttEntity, ClimateEntity): """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 @@ class MqttClimate(MqttEntity, ClimateEntity): 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 @@ class MqttClimate(MqttEntity, ClimateEntity): ): 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 624823e0ebb..c3501267e12 100644 --- a/tests/components/mqtt/test_climate.py +++ b/tests/components/mqtt/test_climate.py @@ -82,9 +82,34 @@ DEFAULT_CONFIG = { "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"') + async_fire_mqtt_message(hass, "current-preset-mode", '{"attribute": "eco"}') 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 + 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 83801d50354..90c5eebaeb2 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 0de580ef7b7..3d65f4eb297 100644 --- a/homeassistant/components/google_cloud/tts.py +++ b/homeassistant/components/google_cloud/tts.py @@ -122,9 +122,13 @@ SUPPORTED_OPTIONS = [ 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 @@ class GoogleCloudTTSProvider(Provider): 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 d963262b712..ac972d24fb5 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 af47f101fbb..c30a7382f27 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 abf08b70dad..c4c444441c2 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 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): 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 017d1ce41e8..ee0f60d92ce 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 c40ad32c078..1a8b7815be4 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 f9ec5321c62..f15045b98da 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 ac972d24fb5..150d7b28949 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 7a1d16c0e87..8f10a13b145 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 321cb7f622b..a2345081b50 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 6c9a7759c3d..a60ac651e31 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 1d6ce675e74..7193d706b89 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 7cbf33f8b47..a032430e1bc 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 13a08685c68..18d5549ef9a 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 b7e7366796b..2fae17ac922 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 2ea4e495a2b..bcefdcf0639 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 4cc13a3057f..8d0dcc334e9 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 17d861d6432..6a7aba16b95 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 bd1a0d2a1e1..150036129d2 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 1d1a4b99705..9cea60ade8c 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 cd0bda6735f..3424aa11a87 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 ce7aebd801a..f29f2e6f6d0 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 b17034e0e8a..ade68284313 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 e26dcbe925a..4d3670ac9b4 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 150d7b28949..e0c0e4c960c 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 8f10a13b145..f6d7d898eb3 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 872f2d0c7a8..eed5a5a5946 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -27,6 +27,7 @@ COMMENT_REQUIREMENTS = ( "envirophat", "evdev", "face_recognition", + "homeassistant-pyozw", "i2csense", "opencv-python-headless", "pybluez", @@ -94,10 +95,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 @@ -111,6 +108,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/tests/components/apns/__init__.py b/tests/components/apns/__init__.py deleted file mode 100644 index 42c980a62a7..00000000000 --- 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 ad55a5697ad..00000000000 --- 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 59c5ebf24a9..1f7e411003a 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 731e413caf8..265ec6f2d1e 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 1afe9617097..a9ad182c4b1 100644 --- a/tests/components/zwave/test_climate.py +++ b/tests/components/zwave/test_climate.py @@ -31,6 +31,9 @@ from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT 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 e8b784feefe..e7283de25b4 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 homeassistant.components.zwave import ( 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 18188cefcd6..d71ba0713d2 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 homeassistant.components.zwave import fan 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 b0114d087ad..745d6d8ce57 100644 --- a/tests/components/zwave/test_init.py +++ b/tests/components/zwave/test_init.py @@ -22,6 +22,9 @@ from homeassistant.util import dt as dt_util 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 74c541f4d5a..87bfb1ec726 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 homeassistant.components.zwave import const, light 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 04d46620013..575df9491ad 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 c47201fb168..56ae0d61d41 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 83ebcaa3a4a..21944fe8f7e 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 4293a4a23fd..4c3efbe61fd 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 2ad94d29b0e..2ffe5d61715 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 @@ from homeassistant.components.zwave.websocket_api import ID, TYPE 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 ec708d38e43..8f84fd6b949 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 ff3712b607e..3479638b387 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 5565b43a78e..89c70eaf83c 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 b6c20bf7542..2b9f1563d99 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 7fc843e4aae..3dbe9f5d8a3 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 ee0f60d92ce..49b02b87f50 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 43c47c2b5e0..a7ad6140fa7 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 3834eb516de..b168923b8b2 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 1a8b7815be4..a52e4a955a6 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 673e77f1630..5fdef0112a1 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 5a8f15f434f..145c2ba4f04 100644 --- a/homeassistant/util/executor.py +++ b/homeassistant/util/executor.py @@ -4,11 +4,11 @@ from __future__ import annotations 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 | 128 ++++++++--- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/roku/test_media_player.py | 205 ++++++++++++++---- 6 files changed, 266 insertions(+), 75 deletions(-) diff --git a/homeassistant/components/roku/const.py b/homeassistant/components/roku/const.py index e399f6e0558..f098483e0c6 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 d68f2b4b242..d03aa9846c3 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 ff9e034e5d4..9cf17d890a4 100644 --- a/homeassistant/components/roku/media_player.py +++ b/homeassistant/components/roku/media_player.py @@ -3,9 +3,12 @@ from __future__ import annotations 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 @@ from homeassistant.components.media_player.const import ( 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 homeassistant.helpers.entity_platform import AddEntitiesCallback 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_ROKU = ( | SUPPORT_BROWSE_MEDIA ) -ATTRS_TO_LAUNCH_PARAMS = { - ATTR_CONTENT_ID: "contentID", - ATTR_MEDIA_TYPE: "MediaType", + +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, } -PLAY_MEDIA_SUPPORTED_TYPES = ( - MEDIA_TYPE_APP, - MEDIA_TYPE_CHANNEL, - MEDIA_TYPE_URL, - FORMAT_CONTENT_TYPE[HLS_PROVIDER], -) +ATTRS_TO_LAUNCH_PARAMS = { + ATTR_CONTENT_ID: "contentID", + ATTR_MEDIA_TYPE: "mediaType", +} -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 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity): ) -> 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 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity): 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 in (MEDIA_TYPE_URL, MEDIA_TYPE_VIDEO): + params = { + 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) - elif media_type == FORMAT_CONTENT_TYPE[HLS_PROVIDER]: - params = { - "MediaType": "hls", - } - - 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 e0c0e4c960c..38629f5e8bd 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 f6d7d898eb3..4b082aa6009 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 2686a281dba..79b996530e3 100644 --- a/tests/components/roku/test_media_player.py +++ b/tests/components/roku/test_media_player.py @@ -27,7 +27,9 @@ from homeassistant.components.media_player.const import ( 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,50 +461,7 @@ async def test_services( "291097", { "contentID": "8e06a8b7-d667-4e31-939d-f40a6dd78a88", - "MediaType": "movie", - }, - ) - - 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_EXTRA: { - ATTR_NAME: "Sent from HA", - ATTR_FORMAT: "mp4", - }, - }, - blocking=True, - ) - - 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", - "videoFormat": "mp4", - }, - ) - - 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", - }, - 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", - { - "MediaType": "hls", + "mediaType": "movie", }, ) @@ -527,6 +486,158 @@ async def test_services( 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: "blah", + ATTR_MEDIA_CONTENT_ID: "https://localhost/media.m4a", + ATTR_MEDIA_EXTRA: { + ATTR_NAME: "Test", + }, + }, + blocking=True, + ) + + assert mock_roku.play_on_roku.call_count == 0 + + await hass.services.async_call( + MP_DOMAIN, + SERVICE_PLAY_MEDIA, + { + 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: content_type, + ATTR_MEDIA_CONTENT_ID: content_id, + }, + blocking=True, + ) + mock_roku.play_on_roku.assert_called_once_with( + content_id, + { + "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_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, + }, + ) + + +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_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.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( hass: HomeAssistant, init_integration: MockConfigEntry, @@ -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 7c3bc63779b..3da9a17c3aa 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -895,7 +895,7 @@ class MQTT: 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 @@ class MQTT: 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 @@ class MQTT: 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 a26e62b6227..23e2a0d1e81 100644 --- a/homeassistant/components/mqtt/config_flow.py +++ b/homeassistant/components/mqtt/config_flow.py @@ -313,7 +313,7 @@ class MQTTOptionsFlowHandler(config_entries.OptionsFlow): 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 b31d90c76f8..11bc0f6839a 100644 --- a/homeassistant/components/mqtt/discovery.py +++ b/homeassistant/components/mqtt/discovery.py @@ -228,13 +228,13 @@ async def async_start( # noqa: C901 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 421ad3203b3..fb25fa1e1b6 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -420,10 +420,7 @@ class MqttAvailability(Entity): 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 @@ class MqttAvailability(Entity): 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 df2ed7e5063..7348e978d02 100644 --- a/homeassistant/components/webostv/notify.py +++ b/homeassistant/components/webostv/notify.py @@ -46,8 +46,8 @@ class LgWebOSNotificationService(BaseNotificationService): 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 a5188545737..7e150c6eb78 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 3dbe9f5d8a3..925571908c5 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 49b02b87f50..9585c8630af 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 a7ad6140fa7..e65c012da85 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 b168923b8b2..494ff28b118 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 5fdef0112a1..6f4e7124d70 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 c6ac7aceae1..3119a7b667b 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 cdf9fa5567b..ca95065e1a9 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 38629f5e8bd..7fdf253ccc0 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 4b082aa6009..d32f2192f5e 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 8a23f7d8df9..fcda7227945 100644 --- a/homeassistant/components/vallox/__init__.py +++ b/homeassistant/components/vallox/__init__.py @@ -29,7 +29,6 @@ from .const import ( 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 @@ PLATFORMS: list[str] = [ 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 @@ class ValloxServiceHandler: 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 5cfa1dae4b5..d6a0ec238c3 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 da063698bd3..e71f3181b55 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 14f33524f52..de554462b42 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 690e47ea816..ae5c5be8082 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 @@ DEVICEINFO_WEBSOCKET_SSL = { } -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 e49b8fdc5ee..12419b21fe8 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 @@ REMOTE_CALL = { } -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 ed09d9e33e0..fe270fed8de 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 @@ from homeassistant.const import ( 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 test_play_media(hass, remote): 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 212ef6c23ca..515e5c0de96 100644 --- a/homeassistant/components/samsungtv/__init__.py +++ b/homeassistant/components/samsungtv/__init__.py @@ -24,6 +24,7 @@ from homeassistant.core import Event, HomeAssistant, callback 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 ed520acc1f7..37a725ee5c9 100644 --- a/homeassistant/components/samsungtv/bridge.py +++ b/homeassistant/components/samsungtv/bridge.py @@ -98,11 +98,16 @@ class SamsungTVBridge(ABC): 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 @@ class SamsungTVBridge(ABC): 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 @@ class SamsungTVLegacyBridge(SamsungTVBridge): # 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 @@ class SamsungTVWSBridge(SamsungTVBridge): # 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 @@ class SamsungTVWSBridge(SamsungTVBridge): 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 595afd061cc..7abff39113b 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 7fdf253ccc0..3f343d5d23f 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 d32f2192f5e..faeba577532 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 ae5c5be8082..bf1587e40dc 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 fe270fed8de..76479dea836 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 603e88fd8c8..5cbc8a1e678 100644 --- a/homeassistant/components/device_automation/__init__.py +++ b/homeassistant/components/device_automation/__init__.py @@ -7,14 +7,14 @@ from enum import Enum 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 homeassistant.requirements import async_get_integration_with_requirements 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 @@ TYPES = { } +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 008a7603dba..f2962d6544e 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 . import ( ) 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 80bed9137d0..06853dd9450 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 3da9a17c3aa..b97a0bc8770 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -50,7 +50,12 @@ from homeassistant.core import ( ) 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 ca9e56f8efb..3bf07db1832 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 78f52e58726..f621021e124 100644 --- a/homeassistant/components/mqtt/device_trigger.py +++ b/homeassistant/components/mqtt/device_trigger.py @@ -23,7 +23,7 @@ from homeassistant.const import ( ) 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 @@ class Trigger: 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 async_setup_trigger(hass, config, config_entry, discovery_data): _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 async_setup_trigger(hass, config, config_entry, discovery_data): 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 e4152250802..186f11534b9 100644 --- a/homeassistant/components/mqtt/tag.py +++ b/homeassistant/components/mqtt/tag.py @@ -5,6 +5,7 @@ import logging 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 @@ class MQTTTagScanner: 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 @@ class MQTTTagScanner: 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 7348e978d02..46f0086e0f6 100644 --- a/homeassistant/components/webostv/notify.py +++ b/homeassistant/components/webostv/notify.py @@ -46,8 +46,8 @@ class LgWebOSNotificationService(BaseNotificationService): 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 | 28 ++++++++++++------- .../lutron_caseta/test_config_flow.py | 6 ++++ 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/lutron_caseta/config_flow.py b/homeassistant/components/lutron_caseta/config_flow.py index b198d5ddbee..74819e25e8e 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 homeassistant.data_entry_flow import FlowResult from .const import ( ABORT_REASON_CANNOT_CONNECT, + BRIDGE_DEVICE_ID, BRIDGE_TIMEOUT, CONF_CA_CERTS, CONF_CERTFILE, @@ -101,7 +104,7 @@ class LutronCasetaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class LutronCasetaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class LutronCasetaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): # 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 @@ class LutronCasetaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class LutronCasetaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): "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], ) + 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() - await bridge.close() - return connected_ok + return None diff --git a/tests/components/lutron_caseta/test_config_flow.py b/tests/components/lutron_caseta/test_config_flow.py index 47956e27002..9dbedeacf5b 100644 --- a/tests/components/lutron_caseta/test_config_flow.py +++ b/tests/components/lutron_caseta/test_config_flow.py @@ -56,6 +56,10 @@ class MockBridge: """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 508b940a541..74687550820 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 6ef46a79101..83ec53ad5a4 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 6ab2d4d873a..ed5cd5fd02c 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 408b4d6adcd..7dbf2d26b59 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 3325c33c349..a29e10db60a 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 193c51f5914..8129a8c9cc2 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 ad203bd266e..15a9f44a98a 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 282549cdeaf..f7beefb2962 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 46c1a8063fd..186bf989a4e 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 7d004af7b54..05945056fb4 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 00000000000..b8fb3b61a77 --- /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 00000000000..efbe2a91eb1 --- /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 00000000000..12a870b4cc9 --- /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 00000000000..db09683450a --- /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 00000000000..7ae1601843e --- /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 00000000000..51f351fb833 --- /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 d8d9570d8ca..a374885e7aa 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 f9a0ae7bd8c..eb0697ca13d 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 7edf0ef50f8..4067110e336 100644 --- a/homeassistant/components/flux_led/select.py +++ b/homeassistant/components/flux_led/select.py @@ -110,7 +110,7 @@ class FluxPowerStateSelect(FluxConfigAtStartSelect, SelectEntity): ) -> 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 @@ class FluxWhiteChannelSelect(FluxConfigAtStartSelect): ) -> 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 81b4a7cf0a6..b64e251bcf0 100644 --- a/tests/components/nest/test_camera_sdm.py +++ b/tests/components/nest/test_camera_sdm.py @@ -10,7 +10,6 @@ from http import HTTPStatus 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 @@ from homeassistant.components.camera import ( 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 @@ DEVICE_TRAITS = { "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 @@ GENERATE_IMAGE_URL_RESPONSE = { 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 9358fe73110..29e31ae4a88 100644 --- a/homeassistant/block_async_io.py +++ b/homeassistant/block_async_io.py @@ -8,7 +8,7 @@ from .util.async_ import protect_loop 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 fbe7a6b005f..b1b638f844a 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 74a8055e971..17a1c0fbfa1 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 400cea3f78c..57c837178a4 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -1213,7 +1213,7 @@ class ConfigFlow(data_entry_flow.FlowHandler): 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 5685b479d1c..38a0bbeb73c 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 @@ class Event: 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 @@ class State: 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 734a568ce4e..b69cf44dc6c 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 c02fa18eefb..f5c68897e2e 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 6ee0b8fefe1..472d399713d 100644 --- a/homeassistant/runner.py +++ b/homeassistant/runner.py @@ -59,7 +59,7 @@ class HassEventLoopPolicy(asyncio.DefaultEventLoopPolicy): # type: ignore[valid @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 @@ class HassEventLoopPolicy(asyncio.DefaultEventLoopPolicy): # type: ignore[valid 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 84971ba89f1..ba4f3d20f9a 100644 --- a/homeassistant/components/diagnostics/util.py +++ b/homeassistant/components/diagnostics/util.py @@ -12,7 +12,7 @@ T = TypeVar("T") @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 f8c14ed8b73..d07d3406073 100644 --- a/homeassistant/components/energy/data.py +++ b/homeassistant/components/energy/data.py @@ -291,7 +291,7 @@ class EnergyManager: "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 c0d9ffcea4a..f8591e5c23f 100644 --- a/homeassistant/components/energy/sensor.py +++ b/homeassistant/components/energy/sensor.py @@ -148,17 +148,17 @@ class SensorManager: 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 976b8cc69f8..509c0cb4083 100644 --- a/homeassistant/components/group/media_player.py +++ b/homeassistant/components/group/media_player.py @@ -123,7 +123,7 @@ class MediaGroup(MediaPlayerEntity): """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 @@ class MediaGroup(MediaPlayerEntity): 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 c6c4d18d21d..f374ebaeb38 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -355,7 +355,7 @@ class SensorEntity(Entity): 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 50d09b207a0..635c5af6242 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 43c3998849c..5e595ca42b7 100644 --- a/homeassistant/components/tts/media_source.py +++ b/homeassistant/components/tts/media_source.py @@ -56,7 +56,7 @@ class TTSMediaSource(MediaSource): 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 ab0a0eaf9a7..47798be3def 100644 --- a/homeassistant/components/zeroconf/usage.py +++ b/homeassistant/components/zeroconf/usage.py @@ -23,5 +23,5 @@ def install_multiple_zeroconf_catcher(hass_zc: HaZeroconf) -> None: 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 dd327acbf75..ef2d21281d1 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 4d3670ac9b4..a2ad73e96c5 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 eed5a5a5946..95a5999aaf1 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -77,7 +77,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, 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 080fffe2107..8b59c38d405 100644 --- a/homeassistant/components/zwave_js/diagnostics.py +++ b/homeassistant/components/zwave_js/diagnostics.py @@ -8,8 +8,8 @@ from zwave_js_server.model.node import NodeDataType 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 d6cc9938eba..e6975ddb47b 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 3f343d5d23f..caa36d4dc46 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 faeba577532..912e41fcd7d 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 c8d8f878c0b..1eb2baf3a02 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 27c3f991d33..646363e3313 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 58608131e90..668d1b9dce9 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 cf7adddc21e..a726175fa38 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 8dff31a5225..afa216cac32 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 b26b69be9ad..98c185fd8d5 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 cd5a6bd4abe..3805394dbce 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 35ce70f617a..42200c2f1d6 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 9e7b05adc34..a0ccd4de9c5 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 b11d2bfd180..3edb0494c37 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 9c2befdf5e8..ac32a9f99bb 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 b6cf59b4226..ddbff0f3ffa 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 1f4f55dd220..d09848fb759 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 58d3f0d06ec..d47896a980a 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 e5f739d63a5..d27338db5a9 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 2b218cd915b..b22c21e4777 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 f85a8e6b005..fedee0f9cf1 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 2646fecfd37..634d8ec9169 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 98ae03afbf2..598650b863c 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 01bad6c4a8f..44b1379ca82 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 4e6ca17e013..a3b34aeedf0 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 22eb05c9ce5..f5560dd7e78 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 8362854a5f6..1596b099ab1 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 da6b4b15459..2161c8e6fe4 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 b41292a15fc..332c8c84635 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 d08c680dfe2..3780b2654f9 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 2d120411513..c632f0fca17 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 | 82 ++-- 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, 1076 insertions(+), 40 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 d5c9f48ee47..8783dd64ab8 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 bad90efee8f..e82bbeaea1b 100644 --- a/homeassistant/components/agent_dvr/camera.py +++ b/homeassistant/components/agent_dvr/camera.py @@ -5,7 +5,7 @@ import logging 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 d5bc3db45f8..ca4af7fd68a 100644 --- a/homeassistant/components/android_ip_webcam/__init__.py +++ b/homeassistant/components/android_ip_webcam/__init__.py @@ -5,7 +5,7 @@ from datetime import timedelta 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 c52aac37a02..2c9a6a52b9e 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 3e7469cff00..632156b7adc 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 3a9c1a6cf91..69588c1b670 100644 --- a/homeassistant/components/mjpeg/camera.py +++ b/homeassistant/components/mjpeg/camera.py @@ -4,7 +4,6 @@ from __future__ import annotations import asyncio from collections.abc import Iterable from contextlib import closing -import logging import aiohttp from aiohttp import web @@ -14,6 +13,7 @@ from requests.auth import HTTPBasicAuth, HTTPDigestAuth 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 @@ from homeassistant.helpers.aiohttp_client import ( 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,32 +61,50 @@ 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) - 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], - ) - ] + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data=config, + ) ) -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()) +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=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 extract_image_from_mjpeg(stream: Iterable[bytes]) -> bytes | None: @@ -124,6 +141,8 @@ class MjpegCamera(Camera): 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 @@ class MjpegCamera(Camera): 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 @@ class MjpegCamera(Camera): 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 @@ class MjpegCamera(Camera): 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 00000000000..2eecdd84d78 --- /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 00000000000..94cd01676ec --- /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 88e4cdba356..02726c3bb3f 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 00000000000..73e6a150a09 --- /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 00000000000..e389850a360 --- /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 00000000000..068d0aafc7e --- /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 acf170472da..e5e4f224fe6 100644 --- a/homeassistant/components/motioneye/camera.py +++ b/homeassistant/components/motioneye/camera.py @@ -24,7 +24,7 @@ from motioneye_client.const import ( ) 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 5e262ff6903..a7f0cf1d7fa 100644 --- a/homeassistant/components/zoneminder/camera.py +++ b/homeassistant/components/zoneminder/camera.py @@ -3,7 +3,7 @@ from __future__ import annotations 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 72037428e68..a4f774e64a7 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -196,6 +196,7 @@ FLOWS = [ "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 00000000000..b3b796dc3b1 --- /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 00000000000..c09bbde2f1d --- /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 00000000000..0678353cf6d --- /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 00000000000..853e0feb687 --- /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 37f3e7ec95f..936940869d6 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 | 55 +++++++++++++++--------- 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/fritz/const.py b/homeassistant/components/fritz/const.py index 0a4e9fd6cd8..635460db15f 100644 --- a/homeassistant/components/fritz/const.py +++ b/homeassistant/components/fritz/const.py @@ -64,3 +64,5 @@ FRITZ_EXCEPTIONS = ( 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 d168878c5da..07eb2eb437f 100644 --- a/homeassistant/components/fritz/switch.py +++ b/homeassistant/components/fritz/switch.py @@ -31,6 +31,7 @@ from .const import ( 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 3bf07db1832..17dbc27f0c4 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 00000000000..ea490783fc0 --- /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 00000000000..bbd42a20c87 --- /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 92884dcef93..a9a96df4f8f 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 564480a0e91..baac9ac19ee 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 8c90efcd44c..1f2645e227c 100644 --- a/homeassistant/components/adguard/__init__.py +++ b/homeassistant/components/adguard/__init__.py @@ -205,7 +205,7 @@ class AdGuardHomeDeviceEntity(AdGuardHomeEntity): 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 b33710bf936..2b1a3bf3a19 100644 --- a/homeassistant/components/arcam_fmj/device_trigger.py +++ b/homeassistant/components/arcam_fmj/device_trigger.py @@ -76,7 +76,7 @@ async def async_attach_trigger( 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 213dc19ff9e..1b1c65ae6d1 100644 --- a/homeassistant/components/azure_devops/__init__.py +++ b/homeassistant/components/azure_devops/__init__.py @@ -123,7 +123,7 @@ class AzureDevOpsDeviceEntity(AzureDevOpsEntity): """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 1a5aa330cc8..27a4eabf535 100644 --- a/homeassistant/components/azure_event_hub/client.py +++ b/homeassistant/components/azure_event_hub/client.py @@ -64,7 +64,7 @@ class AzureEventHubClientSAS(AzureEventHubClient): 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 3f474cdf70b..cab21b63d6d 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 1d45293012c..4b9adf16226 100644 --- a/homeassistant/components/hue/v2/hue_event.py +++ b/homeassistant/components/hue/v2/hue_event.py @@ -47,7 +47,7 @@ async def async_setup_hue_events(bridge: "HueBridge"): 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 9b32bc40043..6866e052368 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 46da1aea0aa..51f2969e9fe 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 4f7a9d6723c..6bc6085d0e5 100644 --- a/homeassistant/components/knx/config_flow.py +++ b/homeassistant/components/knx/config_flow.py @@ -317,7 +317,7 @@ class KNXOptionsFlowHandler(OptionsFlow): 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 ab6b53978be..e661c14a151 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 bccac112d55..aac8c263ef1 100644 --- a/homeassistant/components/nest/config_flow.py +++ b/homeassistant/components/nest/config_flow.py @@ -489,7 +489,7 @@ class NestFlowHandler( 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 146f4b2ff27..b6182d7ed84 100644 --- a/homeassistant/components/norway_air/air_quality.py +++ b/homeassistant/components/norway_air/air_quality.py @@ -106,31 +106,31 @@ class AirSensor(AirQualityEntity): """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 5f26c952b31..af29a9fba99 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 @@ class BaseNotificationService: 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 ebb986d6a7e..12bcec295d0 100644 --- a/homeassistant/components/tibber/sensor.py +++ b/homeassistant/components/tibber/sensor.py @@ -464,7 +464,7 @@ class TibberSensorRT(TibberSensor, update_coordinator.CoordinatorEntity): 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 83f4b820523..33b03109cd8 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 6df95d9bbfc..88c96feea88 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -242,7 +242,7 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): 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 cdff1bac634..126deba3494 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 97647d87106..80a26a56b22 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 caa36d4dc46..c60fb3c8b5b 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 912e41fcd7d..77f59d29676 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 c30a7382f27..af47f101fbb 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 a9ca62ecb09..d98ec4175fc 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 2bb9111b269..412ef74e3b8 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 c60fb3c8b5b..34e32e0d792 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 77f59d29676..ef1240537ec 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 55d6f73108c..579f47ed4a7 100644 --- a/homeassistant/components/recorder/models.py +++ b/homeassistant/components/recorder/models.py @@ -77,7 +77,7 @@ DOUBLE_TYPE = ( ) -class Events(Base): # type: ignore +class Events(Base): # type: ignore[misc,valid-type] """Event history data.""" __table_args__ = ( @@ -141,7 +141,7 @@ class Events(Base): # type: ignore return None -class States(Base): # type: ignore +class States(Base): # type: ignore[misc,valid-type] """State change history.""" __table_args__ = ( @@ -276,13 +276,13 @@ class StatisticsBase: @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 @@ class StatisticsMeta(Base): # type: ignore 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 @@ class RecorderRuns(Base): # type: ignore 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 @@ class SchemaChanges(Base): # type: ignore ) -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 @@ class LazyState(State): 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 @@ class LazyState(State): """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 @@ class LazyState(State): """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 @@ class LazyState(State): """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 e44ae9aafff..dd80fb15479 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 6c305242f5f..4154ae83055 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 _sorted_statistics_to_dict( 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 _sorted_statistics_to_dict( 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 1337aa05895..e4b79ed77a8 100644 --- a/homeassistant/components/xiaomi_miio/fan.py +++ b/homeassistant/components/xiaomi_miio/fan.py @@ -8,12 +8,11 @@ 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 ( +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 239e8c28910..0091d58e1e2 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 a0ff320e228..2b5f6f3d5fd 100644 --- a/homeassistant/components/xiaomi_miio/select.py +++ b/homeassistant/components/xiaomi_miio/select.py @@ -8,7 +8,7 @@ from miio.airhumidifier import LedBrightness as AirhumidifierLedBrightness 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 34e32e0d792..33aed1f238c 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 ef1240537ec..8dad410b49c 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 af0c50ec5fa..554a88f4ad5 100644 --- a/homeassistant/helpers/storage.py +++ b/homeassistant/helpers/storage.py @@ -194,7 +194,11 @@ class Store: 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 5c2882ec2e2..15e9254e9d8 100644 --- a/homeassistant/util/__init__.py +++ b/homeassistant/util/__init__.py @@ -137,7 +137,7 @@ class Throttle: 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 @@ class Throttle: 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 9c98691c605..fdee7a7a90f 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 a1ee2b9f584..aad93e37542 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 dfe73b0e937..e964fee798c 100644 --- a/homeassistant/util/unit_system.py +++ b/homeassistant/util/unit_system.py @@ -134,7 +134,7 @@ class UnitSystem: 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 @@ class UnitSystem: 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 @@ class UnitSystem: 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 @@ class UnitSystem: 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 @@ class UnitSystem: 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 8e9cb382b6c..3eafc8abdd7 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 e6ac5fd364a..84349ef91a9 100644 --- a/homeassistant/util/yaml/loader.py +++ b/homeassistant/util/yaml/loader.py @@ -100,7 +100,7 @@ class SafeLineLoader(yaml.SafeLoader): """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 65b1b657ef4..eaabb002b0a 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 04e11ab99be..cf2d73715de 100644 --- a/homeassistant/helpers/config_entry_oauth2_flow.py +++ b/homeassistant/helpers/config_entry_oauth2_flow.py @@ -217,7 +217,7 @@ class AbstractOAuth2FlowHandler(config_entries.ConfigFlow, metaclass=ABCMeta): ) 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 cbd1f4ffaba..05f16bc06ad 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -15,7 +15,9 @@ import logging 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 _deprecated_or_removed( """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 09345bf51bf..07f5e640ea3 100644 --- a/homeassistant/helpers/data_entry_flow.py +++ b/homeassistant/helpers/data_entry_flow.py @@ -70,7 +70,7 @@ class FlowManagerIndexView(_BaseFlowManagerView): 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 e3f13e3ad16..7937459b50c 100644 --- a/homeassistant/helpers/debounce.py +++ b/homeassistant/helpers/debounce.py @@ -97,7 +97,7 @@ class Debouncer: 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 bf2e13c1e24..bb600556991 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 @@ class Entity(ABC): @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 d1dc11aae4d..e2ebbd31dac 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 13cc32a35b6..44dd21d7fa3 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -152,7 +152,7 @@ class IntentHandler: 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 06fb0760818..bbc32145706 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 79d46f8ec2e..b8262d3a533 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 3cf11453e20..e638288a58c 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 d371017a475..2b93b69fe37 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 31a784a259e..c24535ebd1f 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -13,8 +13,8 @@ from homeassistant.components.sensor import ( 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 @@ 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.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 @@ class MqttSensor(MqttEntity, SensorEntity, RestoreEntity): 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 @@ class MqttSensor(MqttEntity, SensorEntity, RestoreEntity): _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 8a1be6b11e2..b653e04c82e 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 a2345081b50..8492a15a8a3 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 b97a0bc8770..23a1fcc579e 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -56,6 +56,7 @@ from homeassistant.helpers import ( 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 async_subscribe_connection_status( 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 50d6a6e4d19..cafbd66b098 100644 --- a/homeassistant/components/mqtt/device_automation.py +++ b/homeassistant/components/mqtt/device_automation.py @@ -3,8 +3,6 @@ import functools 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 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( 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 f621021e124..71c0a9f9364 100644 --- a/homeassistant/components/mqtt/device_trigger.py +++ b/homeassistant/components/mqtt/device_trigger.py @@ -222,7 +222,7 @@ async def async_setup_trigger(hass, config, config_entry, discovery_data): 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 async_setup_trigger(hass, config, config_entry, discovery_data): 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 fb25fa1e1b6..6f881a70690 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -25,7 +25,7 @@ from homeassistant.const import ( 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 @@ class MqttAvailability(Entity): 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 @@ class MqttDiscoveryUpdate(Entity): 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 @@ class MqttEntity( 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 186f11534b9..4f6f380e47d 100644 --- a/homeassistant/components/mqtt/tag.py +++ b/homeassistant/components/mqtt/tag.py @@ -27,6 +27,7 @@ from .mixins import ( 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 @@ class MQTTTagScanner: 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 @@ class MQTTTagScanner: 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 @@ class MQTTTagScanner: ) 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 4020c2beaeb..3b83581b86a 100644 --- a/tests/components/mqtt/test_device_tracker_discovery.py +++ b/tests/components/mqtt/test_device_tracker_discovery.py @@ -5,6 +5,7 @@ import pytest 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 972b0678ed2..8a3719f1707 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 5d94f349c58..463f3d03fff 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 @@ from homeassistant.const import ( 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 e1f3de83a0d..7d3b4f2e1b2 100644 --- a/tests/components/mqtt/test_tag.py +++ b/tests/components/mqtt/test_tag.py @@ -7,8 +7,10 @@ import pytest 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 46f0086e0f6..82e61856187 100644 --- a/homeassistant/components/webostv/notify.py +++ b/homeassistant/components/webostv/notify.py @@ -7,7 +7,7 @@ from typing import Any 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 @@ class LgWebOSNotificationService(BaseNotificationService): 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 7e150c6eb78..92fb151c1b3 100644 --- a/tests/components/webostv/test_notify.py +++ b/tests/components/webostv/test_notify.py @@ -4,9 +4,13 @@ from unittest.mock import Mock, call 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 b4def6fb379..b70f81bc4d5 100644 --- a/homeassistant/components/tuya/climate.py +++ b/homeassistant/components/tuya/climate.py @@ -365,6 +365,13 @@ class TuyaClimateEntity(TuyaEntity, ClimateEntity): 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 74ca65ade06..a3b4dea8424 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -212,6 +212,10 @@ class CastDevice(MediaPlayerEntity): 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 @@ class CastDevice(MediaPlayerEntity): 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, - ) + 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.async_write_ha_state() - - await self.hass.async_add_executor_job(self._chromecast.disconnect) - self._invalidate() - self.async_write_ha_state() def _invalidate(self): @@ -904,16 +903,13 @@ class DynamicCastGroup: 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 cf72cf38827..1c2da93f0a1 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -38,7 +38,7 @@ from homeassistant.const import ( 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 86e5557160c..d0b7cfc4158 100644 --- a/homeassistant/components/cast/__init__.py +++ b/homeassistant/components/cast/__init__.py @@ -12,7 +12,7 @@ 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 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 6f881a70690..ba67339a5d4 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 901972595e4..86fb797ea31 100644 --- a/homeassistant/components/automation/logbook.py +++ b/homeassistant/components/automation/logbook.py @@ -8,11 +8,11 @@ from .const import DOMAIN @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 f374ebaeb38..3414f13268f 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -397,7 +397,10 @@ class SensorEntity(Entity): # 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 @@ class SensorEntity(Entity): 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 f6f9c968f10..9017c60c23f 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 @@ def sync_entity_lifecycle( 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 bb600556991..8e4f6bc8b58 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -707,10 +707,11 @@ class Entity(ABC): 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 799b209f16e..cc252f82782 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -172,7 +172,7 @@ class EntityPlatform: 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 @@ class EntityPlatform: # 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 cbafd2e7e95..71b2cf1a585 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 async_track_time_interval( 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 async_track_time_interval( 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 38647792b7a..75ca96ea246 100644 --- a/homeassistant/helpers/state.py +++ b/homeassistant/helpers/state.py @@ -102,12 +102,12 @@ async def async_reproduce_state( 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 3c18dcc3278..09a329cd275 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 175a29fcc5d..0b18ad9aa42 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 7b2f963102e..36292989dce 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 8dba52ebe9d..d09feec5237 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 7c52581498b..b61ea443728 100644 --- a/homeassistant/components/intellifire/sensor.py +++ b/homeassistant/components/intellifire/sensor.py @@ -16,11 +16,12 @@ from homeassistant.components.sensor import ( 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 @@ INTELLIFIRE_SENSORS: tuple[IntellifireSensorEntityDescription, ...] = ( 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 cda8f0da1f2..721c51ca964 100644 --- a/homeassistant/components/sisyphus/__init__.py +++ b/homeassistant/components/sisyphus/__init__.py @@ -29,19 +29,15 @@ CONFIG_SCHEMA = vol.Schema( {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 8783dd64ab8..90437cac34e 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 00000000000..844766ceffa --- /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 9b615afbbe1..d81d63e3af1 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 homeassistant.const import ( ) 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 00000000000..6b203f51e98 --- /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 | 49 ++++++-- .../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 | 34 +++--- .../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 | 112 ++++++++---------- .../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 | 45 ++++--- tests/components/sleepiq/test_init.py | 72 ++++++----- tests/components/sleepiq/test_sensor.py | 46 ++++--- 22 files changed, 330 insertions(+), 400 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 e27c9488f0a..dabc32c0e11 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 5a69cfacd11..2fc0c52c706 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 @@ from .coordinator import SleepIQDataUpdateCoordinator _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]) - try: - await hass.async_add_executor_job(client.login) - except ValueError: - _LOGGER.error("SleepIQ login failed, double check your username and password") - return False + conf = entry.data + email = conf[CONF_USERNAME] + password = conf[CONF_PASSWORD] - coordinator = SleepIQDataUpdateCoordinator( - hass, - client=client, - username=entry.data[CONF_USERNAME], - ) + client_session = async_get_clientsession(hass) + + gateway = AsyncSleepIQ(client_session=client_session) + + try: + 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 + + 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 890cd6711a6..d2aeae06e8a 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.components.binary_sensor import ( 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 aff4d7e8dc7..dffb30f39d7 100644 --- a/homeassistant/components/sleepiq/config_flow.py +++ b/homeassistant/components/sleepiq/config_flow.py @@ -3,14 +3,16 @@ from __future__ import annotations 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 @@ class SleepIQFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class SleepIQFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ) -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]) + client_session = async_get_clientsession(hass) - try: - client.login() - except ValueError as error: - return str(error) - - 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 3fc0ae999fd..63e86270925 100644 --- a/homeassistant/components/sleepiq/const.py +++ b/homeassistant/components/sleepiq/const.py @@ -14,3 +14,6 @@ SENSOR_TYPES = {SLEEP_NUMBER: "SleepNumber", IS_IN_BED: "Is In Bed"} 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 467238e907e..ca664f99426 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 350435573f1..42458472057 100644 --- a/homeassistant/components/sleepiq/entity.py +++ b/homeassistant/components/sleepiq/entity.py @@ -1,9 +1,15 @@ """Entity for the SleepIQ integration.""" -from homeassistant.core import callback -from homeassistant.helpers.update_coordinator import CoordinatorEntity +from abc import abstractmethod -from .const import BED, ICON_OCCUPIED, SENSOR_TYPES -from .coordinator import SleepIQDataUpdateCoordinator +from asyncsleepiq import SleepIQBed, SleepIQSleeper + +from homeassistant.core import callback +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, +) + +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 @@ class SleepIQSensor(CoordinatorEntity): 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 a516bd7545b..48ada7b14a2 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 52ded76762d..dd7fdabcfb3 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 33aed1f238c..9f41bd500eb 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 8dad410b49c..4a93ab379f6 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 707fc436c15..b694928a042 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 - -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"] - ] +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(" ", "_") @pytest.fixture -def config_data(): - """Provide configuration data for tests.""" - return { - CONF_USERNAME: "username", - CONF_PASSWORD: "password", - } +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] + + 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 + + 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 f1e59f5ad2d..00000000000 --- 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 5fb12da0507..00000000000 --- 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 1d5c0d89943..00000000000 --- 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 c9b60824115..00000000000 --- 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 a665db7de29..00000000000 --- 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 c009e684220..00000000000 --- 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 ca9bf3c84fc..2b265e19626 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" + assert ( + state.attributes.get(ATTR_FRIENDLY_NAME) + == f"SleepNumber {BED_NAME} {SLEEPER_L_NAME} 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" + 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" - # 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" + 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 e4a422c888f..b2554ea968e 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 @@ SLEEPIQ_CONFIG = { 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"] == {} - 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] + 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 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 15af03e14ce..0aed23c4c50 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.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, -) +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_update_interval(hass: HomeAssistant, setup_entry) -> None: +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, 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 + 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() + 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 + 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 baa8732365b..26ddc9aa485 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 aaae49afb64..03862a6ef5f 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 9f41bd500eb..b3ef6eb2448 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 4a93ab379f6..1c23b20c7ea 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 d82359613f7..193d2b7fa81 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 b3ef6eb2448..4b820df42f0 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 1c23b20c7ea..c2091f02ff9 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 f51266e45dc..0ea1fc1d3f3 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 9a3a88dfc95..6431160f696 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 a2ad73e96c5..62e64066708 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 4b820df42f0..566f72548e3 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 c2091f02ff9..fbd8b2e8982 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 e06344827a7..23605268649 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 3f7744ecbb4..c1ccc5ec954 100644 --- a/homeassistant/components/broadlink/const.py +++ b/homeassistant/components/broadlink/const.py @@ -31,7 +31,7 @@ DOMAINS_AND_TYPES = { "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 9880d0b3c22..9af50a345fb 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 29020b1e905..2b98b757fbd 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 46fdc89871f..95233cca9ca 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 | 116 +++++++++--------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/homeassistant/components/workday/binary_sensor.py b/homeassistant/components/workday/binary_sensor.py index 3119a7b667b..44fb0f871de 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] + 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 = (get_date(dt.now()) + timedelta(days=days_offset)).year - obj_holidays = getattr(holidays, country)(years=year) + 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 + self._attr_extra_state_attributes = { + CONF_WORKDAYS: workdays, + CONF_EXCLUDES: excludes, + CONF_OFFSET: days_offset, + } - @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 - - 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 @@ class IsWorkdaySensor(BinarySensorEntity): 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 @@ class IsWorkdaySensor(BinarySensorEntity): 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_exclude(day_of_week, date): - self._state = False + if self.is_include(day_of_week, adjusted_date): + self._attr_is_on = True + + 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 12e97e16c62..87b6c86fc33 100644 --- a/homeassistant/components/alarmdecoder/binary_sensor.py +++ b/homeassistant/components/alarmdecoder/binary_sensor.py @@ -77,6 +77,7 @@ class AlarmDecoderBinarySensor(BinarySensorEntity): 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 37a725ee5c9..ee5e44f626c 100644 --- a/homeassistant/components/samsungtv/bridge.py +++ b/homeassistant/components/samsungtv/bridge.py @@ -121,6 +121,10 @@ class SamsungTVBridge(ABC): 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 @@ class SamsungTVBridge(ABC): # 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 @@ class SamsungTVBridge(ABC): 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 @@ class SamsungTVLegacyBridge(SamsungTVBridge): """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 @@ class SamsungTVLegacyBridge(SamsungTVBridge): 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 @@ class SamsungTVWSBridge(SamsungTVBridge): """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 @@ class SamsungTVWSBridge(SamsungTVBridge): 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 e856d746b3d..421b88d50ad 100644 --- a/homeassistant/components/samsungtv/media_player.py +++ b/homeassistant/components/samsungtv/media_player.py @@ -13,6 +13,7 @@ from homeassistant.components.media_player import ( 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 @@ class SamsungTVDevice(MediaPlayerEntity): 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 @@ class SamsungTVDevice(MediaPlayerEntity): 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 @@ class SamsungTVDevice(MediaPlayerEntity): 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 @@ class SamsungTVDevice(MediaPlayerEntity): 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 - self.send_key(SOURCES[source]) + if source in SOURCES: + self.send_key(SOURCES[source]) + return + + LOGGER.error("Unsupported source") diff --git a/tests/components/samsungtv/conftest.py b/tests/components/samsungtv/conftest.py index de554462b42..e1cb4f86082 100644 --- a/tests/components/samsungtv/conftest.py +++ b/tests/components/samsungtv/conftest.py @@ -8,6 +8,8 @@ from samsungtvws import SamsungTVWS 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 00000000000..d56e540e64b --- /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 bf1587e40dc..2aea4b22595 100644 --- a/tests/components/samsungtv/test_config_flow.py +++ b/tests/components/samsungtv/test_config_flow.py @@ -44,6 +44,8 @@ from homeassistant.const import ( 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 76479dea836..55d68453a38 100644 --- a/tests/components/samsungtv/test_media_player.py +++ b/tests/components/samsungtv/test_media_player.py @@ -17,6 +17,7 @@ from homeassistant.components.media_player.const import ( 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.helpers.typing import ConfigType 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 87bfc7f89bc..4bb8623acb2 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 566f72548e3..1d7f31af488 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 fbd8b2e8982..b721d5d712a 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 44329c95eb1..d514d54aa66 100644 --- a/homeassistant/components/eq3btsmart/climate.py +++ b/homeassistant/components/eq3btsmart/climate.py @@ -167,8 +167,6 @@ class EQ3BTSmartThermostat(ClimateEntity): 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 05f16bc06ad..30ce647132e 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -939,6 +939,8 @@ def key_dependency( 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 f17b610ff23..38fe621f96c 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 23d8200be23..edf856d6843 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 b98dc07deb6..b3f57656dd7 100644 --- a/homeassistant/auth/__init__.py +++ b/homeassistant/auth/__init__.py @@ -354,7 +354,7 @@ class AuthManager: 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 60f790fa490..bb81d1fb04f 100644 --- a/homeassistant/auth/mfa_modules/__init__.py +++ b/homeassistant/auth/mfa_modules/__init__.py @@ -55,7 +55,7 @@ class MultiFactorAuthModule: @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 71fdbadbb90..37b35a5087d 100644 --- a/homeassistant/auth/mfa_modules/notify.py +++ b/homeassistant/auth/mfa_modules/notify.py @@ -251,7 +251,7 @@ class NotifyAuthModule(MultiFactorAuthModule): 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 a88aa19d742..bb5fc47469f 100644 --- a/homeassistant/auth/mfa_modules/totp.py +++ b/homeassistant/auth/mfa_modules/totp.py @@ -107,7 +107,7 @@ class TotpAuthModule(MultiFactorAuthModule): 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 @@ class TotpAuthModule(MultiFactorAuthModule): 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 @@ class TotpAuthModule(MultiFactorAuthModule): 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 @@ class TotpAuthModule(MultiFactorAuthModule): """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 b209f6b2f05..d80d7a5273b 100644 --- a/homeassistant/auth/providers/__init__.py +++ b/homeassistant/auth/providers/__init__.py @@ -62,7 +62,7 @@ class AuthProvider: @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 @@ class LoginFlow(data_entry_flow.FlowHandler): 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 4fb12e3e764..a4797dd8c10 100644 --- a/homeassistant/auth/providers/homeassistant.py +++ b/homeassistant/auth/providers/homeassistant.py @@ -120,7 +120,7 @@ class Data: @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 42458472057..78c2045bcb6 100644 --- a/homeassistant/components/sleepiq/entity.py +++ b/homeassistant/components/sleepiq/entity.py @@ -4,6 +4,8 @@ from abc import abstractmethod 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 @@ class SleepIQSensor(CoordinatorEntity): 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 6431160f696..e9548c03ba6 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 62e64066708..f542bf408c1 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 1d7f31af488..073e3c6e819 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 b721d5d712a..c65b43a032e 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 | 272 ++++++++++++++++++++-- 3 files changed, 359 insertions(+), 38 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index b1b638f844a..986171cbee7 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 764138ca5f3..a41329a1548 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 @@ class HomeAssistantHTTP: 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 @@ class HomeAssistantHTTP: 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 @@ class HomeAssistantHTTP: 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 @@ class HomeAssistantHTTP: 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 c03c8143bad..79d0a6c4791 100644 --- a/tests/components/http/test_init.py +++ b/tests/components/http/test_init.py @@ -3,11 +3,13 @@ from datetime import timedelta 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 homeassistant.util.ssl import server_context_intermediate, server_context_m 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,62 +140,278 @@ 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 - ) - 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_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): """Test the CORS default settings.""" with patch("homeassistant.components.http.setup_cors") as mock_setup: 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 1af10039772..28b0460ac7a 100644 --- a/homeassistant/components/logbook/__init__.py +++ b/homeassistant/components/logbook/__init__.py @@ -71,7 +71,7 @@ GROUP_BY_MINUTES = 15 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 38a0bbeb73c..27dba3cbc52 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -141,9 +141,12 @@ TIMEOUT_EVENT_START = 15 _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 74687550820..8c36ef66120 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 11e42eaaa09..a8c3543873d 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 ff62d60dafe..18020014268 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 0fa739c6623..f08e02f73c1 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 82257805283..3872752bb61 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 61e5e3b5e96..2c3e42adcc8 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 c95ea1f5082..b3ec1329eae 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 6b76fb0e6bc..40178b203f9 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 c1a78517fa8..5e34b2eec1b 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 82b1f09735b..b4cdaf000ab 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 00000000000..0e88f508191 --- /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 00000000000..5d94ca07873 --- /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 00000000000..3023dd5bdf1 --- /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 00000000000..db0dd06dbbf --- /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 00000000000..d2aa02fe0f4 --- /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 00000000000..0a87f484887 --- /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 00000000000..d38dc06f748 --- /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 00000000000..09eab73359b --- /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 00000000000..622087ad5e5 --- /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 00000000000..cf03121d761 --- /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 00000000000..f54828ea224 --- /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 00000000000..80e624f3d01 --- /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 682ab1728ea..7c92631351e 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 b47c0ea3646..32cc610f596 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 00000000000..c4adcb1bd9e --- /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 00000000000..a974f44967e --- /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 00000000000..35b6807586d --- /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 82754ec0865..86cf9781d3a 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 21a6adca854..8d9cbd0c055 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 e0a95b88daf..ce27ea82abc 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 0e1c6445a9f..1682edb66a3 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -261,12 +261,6 @@ async def async_setup_entry( # noqa: C901 ) ) - # 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_setup_entry( # noqa: C901 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 3780b2654f9..1003316f1e5 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 29d6ced184b..997c3fff2a3 100644 --- a/homeassistant/components/iss/__init__.py +++ b/homeassistant/components/iss/__init__.py @@ -12,7 +12,6 @@ PLATFORMS = [Platform.BINARY_SENSOR] 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 196bdfd055e..12a8a7514b2 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 e47d977b96f..09f7f391b89 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) - assert configured.get("type") == "create_entry" + 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 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 3cebd37bf5d..e8da8738b5b 100644 --- a/homeassistant/components/sabnzbd/__init__.py +++ b/homeassistant/components/sabnzbd/__init__.py @@ -297,6 +297,7 @@ def async_request_configuration(hass, config, host, web_root): 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 8492a15a8a3..b1ff5dca8d1 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 b41a893c7e4..206af776a61 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 f34ae161c6d..53cc89e708e 100644 --- a/homeassistant/components/kaiterra/api_data.py +++ b/homeassistant/components/kaiterra/api_data.py @@ -99,5 +99,7 @@ class KaiterraApiData: 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 38dbc4f0ebc..3962e953520 100644 --- a/homeassistant/components/braviatv/__init__.py +++ b/homeassistant/components/braviatv/__init__.py @@ -110,7 +110,7 @@ class BraviaTVCoordinator(DataUpdateCoordinator[None]): ), ) - 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 0ea4c57d4d9..20083bb3d1c 100644 --- a/homeassistant/components/modbus/modbus.py +++ b/homeassistant/components/modbus/modbus.py @@ -358,7 +358,7 @@ class ModbusHub: 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 18d769df07f..c83ca669d6d 100644 --- a/homeassistant/components/device_tracker/config_entry.py +++ b/homeassistant/components/device_tracker/config_entry.py @@ -16,12 +16,21 @@ from homeassistant.const import ( ) 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 @@ class ScannerEntity(BaseTrackerEntity): 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 216255b9cb6..c52241ae51f 100644 --- a/homeassistant/components/device_tracker/const.py +++ b/homeassistant/components/device_tracker/const.py @@ -37,3 +37,5 @@ ATTR_MAC: Final = "mac" 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 ff67f77257b..a3de0e51708 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 @@ from homeassistant.components.device_tracker.const import ( ATTR_IP, ATTR_MAC, ATTR_SOURCE_TYPE, + CONNECTED_DEVICE_REGISTERED, DOMAIN as DEVICE_TRACKER_DOMAIN, SOURCE_TYPE_ROUTER, ) @@ -42,6 +44,7 @@ from homeassistant.helpers.device_registry import ( 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 @@ class DhcpServiceInfo(BaseServiceInfo): 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 @@ class WatcherBase: 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 @@ class DeviceTrackerWatcher(WatcherBase): 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 3c8efad5b05..5134123074e 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 3650ed32987..d1b8d72be67 100644 --- a/tests/components/dhcp/test_init.py +++ b/tests/components/dhcp/test_init.py @@ -16,6 +16,7 @@ from homeassistant.components.device_tracker.const import ( ATTR_IP, ATTR_MAC, ATTR_SOURCE_TYPE, + CONNECTED_DEVICE_REGISTERED, SOURCE_TYPE_ROUTER, ) from homeassistant.components.dhcp.const import DOMAIN @@ -26,6 +27,7 @@ from homeassistant.const import ( 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 9ee89d248cc..b30237f98da 100644 --- a/homeassistant/components/recorder/pool.py +++ b/homeassistant/components/recorder/pool.py @@ -5,7 +5,7 @@ from sqlalchemy.pool import NullPool, StaticPool 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 cfe0aa370fe..56edf39311c 100644 --- a/homeassistant/components/modbus/__init__.py +++ b/homeassistant/components/modbus/__init__.py @@ -139,9 +139,11 @@ BASE_STRUCT_SCHEMA = BASE_COMPONENT_SCHEMA.extend( 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 dccd2eb4990..d4f0fa6d9ea 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 ca0f370b562..74cd2a49861 100644 --- a/homeassistant/components/modbus/validators.py +++ b/homeassistant/components/modbus/validators.py @@ -58,9 +58,11 @@ OLD_DATA_TYPES = { } 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 02e54c9dd73..5c2d7e3b68c 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 6dd72412fd8..e047c0dafba 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 @@ class DataUpdateCoordinatorMixin: 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 b82724f05d6..35575a442b2 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 e661c14a151..79cac79cb17 100644 --- a/homeassistant/components/lookin/climate.py +++ b/homeassistant/components/lookin/climate.py @@ -152,8 +152,7 @@ class ConditionerEntity(LookinCoordinatorEntity, ClimateEntity): # 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 0408f547f25..ebd9e041332 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 da536c4bd06..d99eb0e4c8c 100644 --- a/homeassistant/components/moehlenhoff_alpha2/climate.py +++ b/homeassistant/components/moehlenhoff_alpha2/climate.py @@ -101,8 +101,7 @@ class Alpha2Climate(CoordinatorEntity, ClimateEntity): 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 fe8a7c2431d..bbd609f5fb8 100644 --- a/homeassistant/components/mold_indicator/sensor.py +++ b/homeassistant/components/mold_indicator/sensor.py @@ -205,9 +205,8 @@ class MoldIndicator(SensorEntity): 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 8c7b86ca173..915cc30897e 100644 --- a/homeassistant/components/motioneye/media_source.py +++ b/homeassistant/components/motioneye/media_source.py @@ -134,8 +134,7 @@ class MotionEyeMediaSource(MediaSource): 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 1083b80ac47..2a5f3850fe4 100644 --- a/homeassistant/components/nest/__init__.py +++ b/homeassistant/components/nest/__init__.py @@ -356,8 +356,7 @@ class NestEventMediaThumbnailView(NestEventViewBase): 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 aac8c263ef1..b257c7b51bb 100644 --- a/homeassistant/components/nest/config_flow.py +++ b/homeassistant/components/nest/config_flow.py @@ -319,8 +319,7 @@ class NestFlowHandler( 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 2676929f2de..af8a4af8ba5 100644 --- a/homeassistant/components/nest/media_source.py +++ b/homeassistant/components/nest/media_source.py @@ -134,8 +134,7 @@ class NestEventMediaStore(EventMediaStore): """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 653ccff6980..de42d19d8c9 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 71a6559975c..24f3086134f 100644 --- a/homeassistant/components/picnic/coordinator.py +++ b/homeassistant/components/picnic/coordinator.py @@ -59,9 +59,7 @@ class PicnicUpdateCoordinator(DataUpdateCoordinator): 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 a7a336e0c1b..130ad110b46 100644 --- a/homeassistant/components/synology_dsm/service.py +++ b/homeassistant/components/synology_dsm/service.py @@ -45,8 +45,7 @@ async def async_setup_services(hass: HomeAssistant) -> 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 659dfcbda16..4c02cdb74dc 100644 --- a/homeassistant/components/tolo/climate.py +++ b/homeassistant/components/tolo/climate.py @@ -144,8 +144,7 @@ class SaunaClimate(ToloSaunaCoordinatorEntity, ClimateEntity): 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 68d5bad27d6..824774f3e31 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 47cdf974cc7..feb5bae98fe 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 70a253d5ceb..0ee3805f42f 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 95e0b059538..67125c45ef5 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -248,8 +248,7 @@ class LgWebOSMediaPlayerEntity(RestoreEntity, MediaPlayerEntity): 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 ae4059728fe..0a21bb37d44 100644 --- a/homeassistant/components/xiaomi_aqara/binary_sensor.py +++ b/homeassistant/components/xiaomi_aqara/binary_sensor.py @@ -333,8 +333,7 @@ class XiaomiDoorSensor(XiaomiBinarySensor, RestoreEntity): 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 48cbc9b270c..ea8e3554746 100644 --- a/homeassistant/components/adax/climate.py +++ b/homeassistant/components/adax/climate.py @@ -146,8 +146,7 @@ class LocalAdaxDevice(ClimateEntity): 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 9b968385602..157b618a264 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 d9cfb744231..5a524851bdf 100644 --- a/homeassistant/components/aurora_abb_powerone/aurora_device.py +++ b/homeassistant/components/aurora_abb_powerone/aurora_device.py @@ -35,8 +35,7 @@ class AuroraEntity(Entity): @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 4145ea8d807..81016bbeb33 100644 --- a/homeassistant/components/balboa/climate.py +++ b/homeassistant/components/balboa/climate.py @@ -99,8 +99,7 @@ class BalboaSpaClimate(BalboaEntity, ClimateEntity): @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 aa295fd0d99..6a015748bd0 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 f96f9d828bb..278ac004121 100644 --- a/homeassistant/components/edl21/sensor.py +++ b/homeassistant/components/edl21/sensor.py @@ -450,9 +450,7 @@ class EDL21Entity(SensorEntity): @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 ff1962aed1b..997f053aa3c 100644 --- a/homeassistant/components/flux_led/__init__.py +++ b/homeassistant/components/flux_led/__init__.py @@ -90,8 +90,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: 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 ee8da5bd66a..5bdd18d1dbd 100644 --- a/homeassistant/components/flux_led/config_flow.py +++ b/homeassistant/components/flux_led/config_flow.py @@ -165,8 +165,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 cd0d9424ead..62b80243c8b 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 07eb2eb437f..3b01ce618b8 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 bbe611c1db9..dbd66e28307 100644 --- a/homeassistant/components/heos/__init__.py +++ b/homeassistant/components/heos/__init__.py @@ -331,8 +331,7 @@ class GroupManager: 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 4f0520eaa72..4a0ad577f90 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 3165280a37b..8c64b9b0443 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 fd404636a22..f83ce7604cf 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 c8dc86c3348..456a10b7630 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 ee0453c9da6..a4b545aa141 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 f779fccdb3b..1d56d493785 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 73aa6f18867..030bb8cdcc8 100644 --- a/homeassistant/components/iaqualink/__init__.py +++ b/homeassistant/components/iaqualink/__init__.py @@ -71,9 +71,7 @@ CONFIG_SCHEMA = vol.Schema( 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 b0161a58251..c61cbef146a 100644 --- a/homeassistant/components/camera/media_source.py +++ b/homeassistant/components/camera/media_source.py @@ -47,14 +47,12 @@ class CameraMediaSource(MediaSource): 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 2cf7005cb66..e5bf9e9b93d 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 c83ca669d6d..adabd297c55 100644 --- a/homeassistant/components/device_tracker/config_entry.py +++ b/homeassistant/components/device_tracker/config_entry.py @@ -141,14 +141,11 @@ def _async_register_mac( 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 b08c521537d..a3f1e5fe272 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 @@ class DownloadDiagnosticsView(http.HomeAssistantView): 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 @@ class DownloadDiagnosticsView(http.HomeAssistantView): 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 23a1fcc579e..197cf26b41f 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 ba67339a5d4..9f3722a8f31 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 abe3d29c607..5e6629ca2a2 100644 --- a/homeassistant/components/tts/__init__.py +++ b/homeassistant/components/tts/__init__.py @@ -344,9 +344,7 @@ class SpeechManager: 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 5e595ca42b7..48bb43990ef 100644 --- a/homeassistant/components/tts/media_source.py +++ b/homeassistant/components/tts/media_source.py @@ -94,9 +94,7 @@ class TTSMediaSource(MediaSource): ) -> 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 a008a30007a..5a11bf2068d 100644 --- a/homeassistant/components/zone/trigger.py +++ b/homeassistant/components/zone/trigger.py @@ -79,8 +79,7 @@ async def async_attach_trigger( ): 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 2eb440cec90..05df480a487 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 0419824492a..8dd127502e2 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 dd6c8fe1cbd..205b5ddfa61 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 80ac8133415..a6cde0c4670 100644 --- a/homeassistant/components/shelly/binary_sensor.py +++ b/homeassistant/components/shelly/binary_sensor.py @@ -188,6 +188,33 @@ RPC_SENSORS: Final = { }, 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 12f82016a1c..51e0711b035 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 @@ class ShellyRpcAttributeEntity(ShellyRpcEntity, entity.Entity): """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 bd8553359cf..cd148430340 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 dabc32c0e11..d80ca44f894 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 00000000000..4e86726ccc8 --- /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 00000000000..526bf004fd5 --- /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 00000000000..9c908da6068 --- /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 00000000000..7997e9c4b5d --- /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 00000000000..64ada3925f3 --- /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 00000000000..356d161f006 --- /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 00000000000..16986efc206 --- /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 a4f774e64a7..aa773be82f0 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -257,6 +257,7 @@ FLOWS = [ "progettihwsw", "prosegur", "ps4", + "pure_energie", "pvoutput", "pvpc_hourly_pricing", "rachio", diff --git a/homeassistant/generated/zeroconf.py b/homeassistant/generated/zeroconf.py index 93a24520497..9c3776155e1 100644 --- a/homeassistant/generated/zeroconf.py +++ b/homeassistant/generated/zeroconf.py @@ -175,6 +175,10 @@ ZEROCONF = { "manufacturer": "nettigo" } }, + { + "domain": "pure_energie", + "name": "smartbridge*" + }, { "domain": "rachio", "name": "rachio*" diff --git a/mypy.ini b/mypy.ini index 6187f296ec2..95e2090f061 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 073e3c6e819..989addfe730 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 c65b43a032e..47217c2c530 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 00000000000..ee7ccbfb483 --- /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 00000000000..4bb89860ce3 --- /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 00000000000..3580d4066ac --- /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 00000000000..a0268d666ba --- /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 00000000000..441a5977a2d --- /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 00000000000..c5ffc19c640 --- /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 00000000000..dddafa3c24b --- /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 fc5c8a598cc..f5d38f4d073 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 989addfe730..9adb28cc685 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 47217c2c530..1afbb1adff8 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 2fc0c52c706..26557ca6daf 100644 --- a/homeassistant/components/sleepiq/__init__.py +++ b/homeassistant/components/sleepiq/__init__.py @@ -22,7 +22,7 @@ from .coordinator import SleepIQDataUpdateCoordinator _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 @@ CONFIG_SCHEMA = vol.Schema( ) -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 00000000000..8cdc0398c2d --- /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 78c2045bcb6..141b94fa72d 100644 --- a/homeassistant/components/sleepiq/entity.py +++ b/homeassistant/components/sleepiq/entity.py @@ -5,7 +5,7 @@ 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.entity import DeviceInfo, Entity from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, DataUpdateCoordinator, @@ -14,6 +14,20 @@ from homeassistant.helpers.update_coordinator import ( 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 @@ class SleepIQSensor(CoordinatorEntity): 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 @@ class SleepIQSensor(CoordinatorEntity): 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 b694928a042..3669fd5a7fc 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 00000000000..cab3f36d73f --- /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 0963a4f927e..a9629ba42f4 100644 --- a/homeassistant/components/sensibo/climate.py +++ b/homeassistant/components/sensibo/climate.py @@ -5,7 +5,7 @@ import asyncio 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 @@ class SensiboClimate(CoordinatorEntity, ClimateEntity): 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 8544972baee..f970581e2a8 100644 --- a/homeassistant/components/sensibo/config_flow.py +++ b/homeassistant/components/sensibo/config_flow.py @@ -6,7 +6,8 @@ import logging 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 41c44e741e9..c79fb5b7bb5 100644 --- a/homeassistant/components/sensibo/coordinator.py +++ b/homeassistant/components/sensibo/coordinator.py @@ -4,7 +4,8 @@ from __future__ import annotations 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 @@ class SensiboDataUpdateCoordinator(DataUpdateCoordinator): 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 71fd3e1c241..e17c5339d67 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 9adb28cc685..bea0716741f 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 1afbb1adff8..7acb27ca344 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 cf3716f09e4..9c59fc70763 100644 --- a/tests/components/sensibo/test_config_flow.py +++ b/tests/components/sensibo/test_config_flow.py @@ -5,7 +5,7 @@ import asyncio 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 897afc96665..4a06e597c36 100644 --- a/homeassistant/components/mill/climate.py +++ b/homeassistant/components/mill/climate.py @@ -184,7 +184,7 @@ class MillHeater(CoordinatorEntity, ClimateEntity): 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 6095bfe5647..36a8cd24213 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -520,7 +520,6 @@ VACUUM_SENSORS = { 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 @@ VACUUM_SENSORS = { 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 @@ VACUUM_SENSORS = { 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 @@ VACUUM_SENSORS = { 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 90437cac34e..da4bdaede00 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 2de42c05dde..99d493a75c4 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -999,25 +999,7 @@ class MediaPlayerEntity(Entity): 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 26a158d240f..44bca818333 100644 --- a/homeassistant/components/plex/__init__.py +++ b/homeassistant/components/plex/__init__.py @@ -48,6 +48,7 @@ from .errors import ShouldUpdateConfigEntry 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 0bff5cfb5cd..11599086179 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 browse_media( # noqa: C901 "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 1ff58ed468d..9b8b0df14c7 100644 --- a/homeassistant/components/plex/media_player.py +++ b/homeassistant/components/plex/media_player.py @@ -585,17 +585,3 @@ class PlexMediaPlayer(MediaPlayerEntity): 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 00000000000..3cb7d40b2de --- /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 c61b4b24a30..fb9ebc70408 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 f542bf408c1..cf95644867b 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 bea0716741f..19a3d64dfae 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 7acb27ca344..f0aae26d5f0 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 3b88f9c0288..4dcbf7fe048 100644 --- a/homeassistant/components/rss_feed_template/__init__.py +++ b/homeassistant/components/rss_feed_template/__init__.py @@ -81,24 +81,31 @@ class RssView(HomeAssistantView): """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 bdc894c3343..ffdb4e5ba9a 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 0d90d89ce98..b06ec499a24 100644 --- a/homeassistant/components/sma/sensor.py +++ b/homeassistant/components/sma/sensor.py @@ -84,7 +84,12 @@ class SMAsensor(CoordinatorEntity, SensorEntity): @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 58fafe930c7..9e4149b0720 100644 --- a/tests/components/sma/test_sensor.py +++ b/tests/components/sma/test_sensor.py @@ -4,6 +4,6 @@ from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT, POWER_WATT 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 e17c5339d67..4d41a6e3ca0 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 19a3d64dfae..36acde01378 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 f0aae26d5f0..b4716b5928b 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 530d45750f5..d2a77080672 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 36acde01378..df323cbd74b 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 b4716b5928b..201c2361c0f 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 d03aa9846c3..4918e7742be 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 df323cbd74b..b743bc8a55d 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 201c2361c0f..21aca60f3bd 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 00000000000..2230fa979b4 --- /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 00000000000..2e90f28e92a --- /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 00000000000..8e0bc629a13 --- /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 2f96095a97c..328bfae9220 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 00000000000..2c3ed1dd930 --- /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 00000000000..892b8b2cd91 --- /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 3513cc855dd..146c37f16d1 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 00000000000..892b8b2cd91 --- /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 00000000000..3c287c2d9d2 --- /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 00000000000..ba2680ac75e --- /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 00000000000..8e0bc629a13 --- /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 e8968158682..8ca060f720e 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 00000000000..df686b2a565 --- /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 00000000000..5ada995aa6e --- /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 a54eab486e6..7a94a9c6dfa 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 00000000000..22c02bbfec3 --- /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 b0aeb95fcb9..d01fe947a8e 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 00000000000..9b801344831 --- /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 00000000000..6459ddb3ba0 --- /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 00000000000..a474631a7f8 --- /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 6ca86ce0542..0157f30c0f0 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 00000000000..c19b1a0b70c --- /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 00000000000..01c13a4f11e --- /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 00000000000..86bba63ac49 --- /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 00000000000..e0e6b1c5bda --- /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 00000000000..b41d6edbd4b --- /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 00000000000..72b0304f1c3 --- /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 00000000000..39d2e182c40 --- /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 00000000000..892b8b2cd91 --- /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 00000000000..71a7aea5018 --- /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 00000000000..81532ef4801 --- /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 00000000000..e1bf8e7f45f --- /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 0b08d9bbc69..94332a74183 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 0a66d494813..5861357a6d4 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 7fa1e0d7c46..c2f686c11dc 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 00000000000..a8cf7db8bbf --- /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 00000000000..dabf3e7e933 --- /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 7255fe17794..79f3f80eca4 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 00000000000..53eb88bf838 --- /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 00000000000..71a7aea5018 --- /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 00000000000..e1bf8e7f45f --- /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 85d796ff6dd..14320019471 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 00000000000..892b8b2cd91 --- /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 00000000000..5ada995aa6e --- /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 00000000000..5ada995aa6e --- /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 6ee951350ca..1d8e9c720e8 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 00000000000..e237bd34b0a --- /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 00000000000..72df4a96818 --- /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 00000000000..71a7aea5018 --- /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 360b9c91d68..e0dc11ae470 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 00000000000..358fdc848ff --- /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 935a234be85..95124984c3d 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 00000000000..72df4a96818 --- /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 00000000000..71a7aea5018 --- /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 00000000000..892b8b2cd91 --- /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 00000000000..31db40bd160 --- /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 00000000000..d77712e768a --- /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 00000000000..e227301685b --- /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 c640d7e0411..a130ea4b90b 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 00000000000..892b8b2cd91 --- /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 00000000000..8e0bc629a13 --- /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 8f88377860b..2f087600355 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 00000000000..4af875cd1ab --- /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 954168d1ee2..e8a60b65e82 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 00000000000..915531f8a35 --- /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 00000000000..ff853127803 --- /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 92cea7966a3..0ffffeb9f04 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 00000000000..5ada995aa6e --- /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 b1621652c95..9cdc9fe0054 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 f1e209b21d8..1f14cdd8eff 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 00000000000..72b0304f1c3 --- /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 ae089710c8a..c4113f251c1 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 00000000000..a1222826481 --- /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 00000000000..81684739474 --- /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 16e0c74c206..8bfb96a65bb 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 00000000000..bee0999420f --- /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 00000000000..9273954369f --- /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 00000000000..e1bf8e7f45f --- /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 5d07abe3507..68d64178efe 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 00000000000..5ada995aa6e --- /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 381078f6d8d..c0695ac82b0 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 6c5446fe9f8..7bdf0e12aa7 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 1faba4734f5..20dd21e72e2 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 00000000000..5ada995aa6e --- /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 8078da1b1a2..9ab3eefa6a6 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 00000000000..e343d2e8b31 --- /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 00000000000..9d5ee388dc3 --- /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 00000000000..1a3e5d67caa --- /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 00000000000..64731388e98 --- /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 00000000000..892b8b2cd91 --- /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 00000000000..0b7bf878ea9 --- /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 792d5297a90..a32716878da 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 00000000000..ae37c2ed275 --- /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 c6de5f984fe..a8d3fa8e922 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 00000000000..af15f92c2f2 --- /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 00000000000..71a7aea5018 --- /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 00000000000..e6945904d90 --- /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 00000000000..af15f92c2f2 --- /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 fc4a7d5bf8c..c6885e06851 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 00000000000..ca12462f388 --- /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 00000000000..e1bf8e7f45f --- /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 00000000000..5ada995aa6e --- /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 00000000000..879d148fd13 --- /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 00000000000..39d2e182c40 --- /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 00000000000..5ada995aa6e --- /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 00000000000..72b0304f1c3 --- /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 18c2f0869bd..54b55bb8324 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 00000000000..5ada995aa6e --- /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 23aa89e12f2..e3f6cf1d39e 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 00000000000..71a7aea5018 --- /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 00000000000..e6945904d90 --- /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 00000000000..bee0999420f --- /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 0b970643bbe..d1c9b95470f 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 00000000000..939157fb837 --- /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 00000000000..8bbcb516b56 --- /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 ffca8cab545..3ee7643c936 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 00000000000..892b8b2cd91 --- /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 00000000000..31baab7883d --- /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 00000000000..ff853127803 --- /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 75ad51d8a1e..9afc3d85536 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 00000000000..9d83bc4b756 --- /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 00000000000..e0e6b1c5bda --- /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 00000000000..1145b3bb9f8 --- /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 4fcafe6fcce..773ee67a7cf 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 00000000000..3b814303e69 --- /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 1fc438ae03f..558729d4040 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 00000000000..af15f92c2f2 --- /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 00000000000..f04d4a327f4 --- /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 662258abf46..ab50cc0e5e8 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 00000000000..39d2e182c40 --- /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 4d39a29a7c3..2f2735f3c45 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 00000000000..af15f92c2f2 --- /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 2ff8f1a2c5a..3d80794d682 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 00000000000..5ada995aa6e --- /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 d16fc316b41..7137da9d868 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 00000000000..bee0999420f --- /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 00000000000..52d93a1a18e --- /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 57cf824fd44..cb9c7a2788b 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 02c83a6e916..31a4cdfdf03 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 00000000000..d2e4793a68b --- /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 ed7ca36d8c2..3f3d2556456 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 00000000000..b41d6edbd4b --- /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 00000000000..bcfc3880d99 --- /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 00000000000..45123261c43 --- /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 d17c5f3a43c..738b64c6409 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 e6c5379e30e..5e1483813a8 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 00000000000..c2f015fe339 --- /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 1801c0d3b92..a4bad4b7c9f 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 18c2f0869bd..54b55bb8324 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 00000000000..5ada995aa6e --- /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 00000000000..c19b1a0b70c --- /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 00000000000..97ac5f20ed2 --- /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 00000000000..bee0999420f --- /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 00000000000..48638e08787 --- /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 00000000000..5ada995aa6e --- /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 00000000000..0b7bf878ea9 --- /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 1708abfe750..cee67307991 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 7da30f4a1be..b53e31046b6 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 3912c15c86c..424ac0d9252 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 00000000000..5ada995aa6e --- /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 12cde31ea6d..2b4ce98a901 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 00000000000..5ada995aa6e --- /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 b61660d41c4..4b712187d4c 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 00000000000..a8223b5b2e3 --- /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 00000000000..892b8b2cd91 --- /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 00000000000..5ada995aa6e --- /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 00000000000..d30ed436a4f --- /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 77ccacd89d6..e6180bc7ead 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 30d1c5e01e7..a3635ae5e2e 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 00000000000..c563a509f07 --- /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 00000000000..e227301685b --- /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 44996764873..f25c2c4dcbc 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 00000000000..5ada995aa6e --- /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 00000000000..e5a635afe10 --- /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 ad89d55c511..82ab5db4452 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 00000000000..892b8b2cd91 --- /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 00000000000..5ada995aa6e --- /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 00000000000..c9a6f8f2462 --- /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 00000000000..5ada995aa6e --- /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 00000000000..5ada995aa6e --- /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 00000000000..892b8b2cd91 --- /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 00000000000..5ada995aa6e --- /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 00000000000..31c65f915dd --- /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 00000000000..6668aaa92fb --- /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 d037c2e07fd..d5a3c1815e1 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 00000000000..ab39cbe9c5e --- /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 00000000000..51eaba460d8 --- /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 00000000000..5ada995aa6e --- /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 00000000000..2c3ed1dd930 --- /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 00000000000..892b8b2cd91 --- /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 00000000000..5ada995aa6e --- /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 a1cdfb27918..ee01e31d21c 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 00000000000..5ada995aa6e --- /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 00000000000..50dcaf1b95f --- /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 0188d8032e6..316feac28c6 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 00000000000..561644de2dd --- /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 803b5e1231a..63db1d8237c 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 00000000000..520a3afd6d9 --- /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 385d9d8973d..263ce28f293 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 d998a1a9d74..5f30894b381 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 00000000000..f8b6dfeea81 --- /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 00000000000..c043ef9ff19 --- /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 6ee3d3c4dc1..0a3c7ec802e 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 00000000000..55d4920e30a --- /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 00000000000..492ab052d9a --- /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 a1adaa91c18..330cd15fb59 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 00000000000..3b814303e69 --- /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 00000000000..abb3969f6b4 --- /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 00000000000..6f753f20966 --- /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 37b91a3dce8..7b04113d7a2 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 e5dc88173d4..d6315fef3d1 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 00000000000..af15f92c2f2 --- /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 00000000000..00616fbdd50 --- /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 db0dd06dbbf..404adec7a94 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 00000000000..2d53457d277 --- /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 00000000000..701049ceac4 --- /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 00000000000..4a2050c2353 --- /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 00000000000..1416bc3ca00 --- /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 467536cc5ec..2e39d1ab16c 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 00000000000..3c056bf4f85 --- /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 00000000000..f7ef4cd289d --- /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 00000000000..892b8b2cd91 --- /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 f820e4ef03e..edd1051671c 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 00000000000..e58538162d7 --- /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 de36fc19681..3ecf4413e11 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 00000000000..71a7aea5018 --- /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 5b06a6c3d01..ffffd249c09 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 00000000000..e01295844ec --- /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 00000000000..71a7aea5018 --- /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 00000000000..2c3ed1dd930 --- /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 00000000000..72df4a96818 --- /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 00000000000..71a7aea5018 --- /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 5112f61ef9f..1829e51815d 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 00000000000..c2f015fe339 --- /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 00000000000..520a3afd6d9 --- /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 cbba19dac1d..a0fe869cd36 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 00000000000..029432864bf --- /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 7857e345165..5233fc3e239 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 640cf82fba1..fb93783d124 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 00000000000..0f73e9340d9 --- /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 00000000000..ea85ad39b4a --- /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 00000000000..5ada995aa6e --- /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 26e4eed8900..7effe92ee63 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 00000000000..af15f92c2f2 --- /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 00000000000..ff853127803 --- /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 370bbd900c2..1ddcf479295 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 00000000000..71a7aea5018 --- /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 00000000000..5ada995aa6e --- /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 00000000000..16e76236805 --- /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 00000000000..434ad4a26b2 --- /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 00000000000..abb3969f6b4 --- /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 f8d2b4b460e..2d28d795e04 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 00000000000..39d2e182c40 --- /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 4134a04508f..fa519dfe6e1 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 00000000000..5ada995aa6e --- /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 00000000000..5ada995aa6e --- /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 00000000000..c19b1a0b70c --- /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 00000000000..bd43098e555 --- /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 99331948120..8bc93c4a3d3 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 00000000000..f9a7d7d07bf --- /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 ac63fdd0fbf..99defca3e95 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 00000000000..1145b3bb9f8 --- /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 00000000000..e7a2eaabb7b --- /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 0a674b6aff6..5e7d7b2d7cc 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 00000000000..19eed2a3fe1 --- /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 00000000000..f14039f810f --- /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 253dac9764d..95baa636720 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 00000000000..71a7aea5018 --- /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 00000000000..5ada995aa6e --- /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 bf45bc1b864..6db4a0fbcba 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 00000000000..bee0999420f --- /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 b512655a7d7..82c79d53acc 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 00000000000..af15f92c2f2 --- /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 d3e7ea45706..27de1a7fb0d 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 00000000000..af15f92c2f2 --- /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 8332248a6c6..623af989f30 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 00000000000..4d37397c800 --- /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 ce11c538997..988637d0977 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 00000000000..1c63a1923bd --- /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 bd62f226b2e..bd5e517481f 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 de85391a7d9..f5e7e538f84 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 00000000000..68438cbdfb0 --- /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 82588fd8951..a01a22cda23 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 00000000000..7124f1e5e28 --- /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 00000000000..ee5407aae19 --- /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 00000000000..c19b1a0b70c --- /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 00000000000..72b0304f1c3 --- /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 00000000000..71a7aea5018 --- /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 e2d0eeae561..e2d1540a3c1 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 00000000000..892b8b2cd91 --- /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 00000000000..71a7aea5018 --- /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 00000000000..fa12207330b --- /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 00000000000..088aa6f754b --- /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 16986efc206..6773cf51478 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 00000000000..4df06e2ca04 --- /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 00000000000..148cd129376 --- /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 00000000000..7673757b245 --- /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 00000000000..4eba3bdc8bb --- /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 00000000000..ff853127803 --- /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 686a0d72c44..eab8417f7fa 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 00000000000..5ada995aa6e --- /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 00000000000..7fd0d4942e8 --- /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 00000000000..d1d6ad72898 --- /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 00000000000..e343d2e8b31 --- /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 00000000000..72df4a96818 --- /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 00000000000..71a7aea5018 --- /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 0c83d2de637..ed809f65e7c 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 00000000000..5ada995aa6e --- /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 00000000000..5ada995aa6e --- /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 00000000000..72b0304f1c3 --- /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 204ada67d7c..02e2d48ac9c 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 00000000000..bee0999420f --- /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 fe1e0e9a73d..3192242fd21 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 873f82d4f68..ab245da07ee 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 00000000000..5ada995aa6e --- /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 86d49fd1f60..cc203d3a8a5 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 00000000000..d19ecb22698 --- /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 00000000000..5ada995aa6e --- /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 74db188f67a..5ede2b35303 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 00000000000..d4a3e2e9fbb --- /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 00000000000..f547d5e3a90 --- /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 00000000000..72b0304f1c3 --- /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 00000000000..694f006218b --- /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 00000000000..71a7aea5018 --- /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 e3f1215d6f2..d7b817eb994 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 e8f1b53c6ea..975011bf88e 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 00000000000..a019d22d264 --- /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 00000000000..7940c6378fe --- /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 6a6f656db76..be8651c1043 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 00000000000..892b8b2cd91 --- /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 00000000000..f59069125d0 --- /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 00000000000..49be6d1efde --- /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 00000000000..5ada995aa6e --- /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 00000000000..0b7bf878ea9 --- /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 00000000000..5ada995aa6e --- /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 00000000000..72b0304f1c3 --- /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 f7da38143ea..ff993f6176e 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 00000000000..534c1e859ee --- /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 00000000000..f8b6dfeea81 --- /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 00000000000..81532ef4801 --- /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 00000000000..73452c7fea7 --- /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 00000000000..7c6659c2e0b --- /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 9970910c04d..53300521d3f 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 00000000000..7940c6378fe --- /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 a3ea32fadc3..f4add9bb240 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 00000000000..892b8b2cd91 --- /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 61fd1656442..9f2e620391a 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 00000000000..91a46a2787f --- /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 00000000000..c19b1a0b70c --- /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 e2fa45d9190..90dc6fee033 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 00000000000..1145b3bb9f8 --- /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 00000000000..6e1cd075aa9 --- /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 00000000000..5ada995aa6e --- /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 69cd1b1623a..3b1ea17e15a 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 00000000000..63ae49fe727 --- /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 9608dfe0cc1..806a63c257a 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 00000000000..85b770fe2ed --- /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 00000000000..5ada995aa6e --- /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 00000000000..bee0999420f --- /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 00000000000..5ada995aa6e --- /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 00000000000..5ada995aa6e --- /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 00000000000..af15f92c2f2 --- /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 00000000000..a679dc35fe3 --- /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 00000000000..5ada995aa6e --- /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 00000000000..3d28cc36f74 --- /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 00000000000..e9c37059842 --- /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 00000000000..372a54786bd --- /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 00000000000..276eac51dd4 --- /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 00000000000..5ada995aa6e --- /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 00000000000..3bfe94e68dd --- /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 00000000000..4eba3bdc8bb --- /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 00000000000..5ada995aa6e --- /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 00000000000..e1bf8e7f45f --- /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 00000000000..13ca7333f5c --- /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 00000000000..d30ed436a4f --- /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 00000000000..59d045e7603 --- /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 ccbc6049794..3e05e3e9e7a 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 d82d7a5500c..4373918fcad 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 00000000000..f8b6dfeea81 --- /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 00000000000..299acb612fb --- /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 00000000000..ff853127803 --- /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 8ad9e051064..c2c0bf5ead0 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 498610b5896..134104c26d6 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 00000000000..731004b0ebc --- /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 7f23e409165..9a406ffcb4b 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 a77dd8851fe..a7d7fec3633 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 2724fad6898..23d8822116c 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 9f791196b75..df951b1e617 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 00000000000..da71ce60d66 --- /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 b4ef19257a2..ea9810962f8 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 00000000000..3b59b1ff213 --- /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 00000000000..5ada995aa6e --- /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 00000000000..793f8eff278 --- /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 00000000000..a41b646034b --- /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 ccfe59404c3..2a364830dbf 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 00000000000..af15f92c2f2 --- /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 00000000000..e1bf8e7f45f --- /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 fcd56ed621e..1039fb01ccd 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 00000000000..0f898b977ee --- /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 00000000000..c043ef9ff19 --- /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 00000000000..d68d88fd2cd --- /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 d92df8c0341..b7bc1745d7d 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 00000000000..7afa1eaea6e --- /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 cf0c66749dd..f2b8c74c25a 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 f4d134979ce..a67e2504cf6 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 00000000000..171a5a2c708 --- /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 00000000000..d3bc93c4168 --- /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 c0cd5557731..8a6bf7c8ddd 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 905ecbe8e4b..9ce0080b218 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 00000000000..892b8b2cd91 --- /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 00000000000..72df4a96818 --- /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 00000000000..71a7aea5018 --- /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 00000000000..1d9ecbee3fc --- /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 00000000000..ce32d575ee2 --- /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 ef9650f28ae..4ab388e95df 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 00000000000..57685686065 --- /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 00000000000..5ada995aa6e --- /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 00000000000..892b8b2cd91 --- /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 a7d70b00a21..d983f0aa48d 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 a2516964840..dd48cf243c2 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 00000000000..af15f92c2f2 --- /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 18c2f0869bd..7f545aa35b4 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 d752c023c72..24c5868d0e9 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 00000000000..5ada995aa6e --- /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 00000000000..c19b1a0b70c --- /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 3d7f30e39df..74252f1b493 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 00000000000..299acb612fb --- /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 ec275b93330..69b6cd221ee 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 3f6dcb8b015..d8802b2bfb8 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 00000000000..022e4103fb7 --- /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 00000000000..00dddc88d1d --- /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 68bca5c452f..6caacd91b64 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 00000000000..793f8eff278 --- /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 1dbdef83eea..91ade7d132b 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 00000000000..af15f92c2f2 --- /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 00000000000..5272ec1315a --- /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 00000000000..2c3ed1dd930 --- /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 f53db0f9721..9819295ee1f 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 2cf46d54165..663137ffa49 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 00000000000..833d18faafb --- /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 00000000000..4fdf62b3c8c --- /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 7794162d32c..021b986f82e 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 b743bc8a55d..8796d7556f5 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 21aca60f3bd..6399e9611f2 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 8d187e9b476..ca4d0460173 100644 --- a/tests/components/wiz/__init__.py +++ b/tests/components/wiz/__init__.py @@ -171,6 +171,11 @@ def _mocked_wizlight(device, extended_white_range, bulb_type) -> wizlight: 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 00000000000..c993072bc07 --- /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 77b3b24331b..ff8cffcf7fa 100644 --- a/homeassistant/components/nest/climate_sdm.py +++ b/homeassistant/components/nest/climate_sdm.py @@ -16,6 +16,7 @@ from google_nest_sdm.thermostat_traits import ( 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 @@ class ThermostatEntity(ClimateEntity): 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 6b100969ea9..5f3efa362b3 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 9ffa48a032a..f766f816109 100644 --- a/tests/components/modbus/test_fan.py +++ b/tests/components/modbus/test_fan.py @@ -16,26 +16,21 @@ from homeassistant.components.modbus.const import ( 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 451d0beca13..220924733ad 100644 --- a/tests/components/modbus/test_light.py +++ b/tests/components/modbus/test_light.py @@ -15,27 +15,22 @@ from homeassistant.components.modbus.const import ( 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 15a41956d3f..86a26e18742 100644 --- a/tests/components/modbus/test_switch.py +++ b/tests/components/modbus/test_switch.py @@ -17,7 +17,6 @@ from homeassistant.components.modbus.const import ( 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 @@ from homeassistant.const import ( 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 20a978f9fda..a4118a0ce78 100644 --- a/homeassistant/components/dlna_dmr/const.py +++ b/homeassistant/components/dlna_dmr/const.py @@ -21,6 +21,11 @@ DEFAULT_NAME: Final = "DLNA Digital Media Renderer" 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 885b7e3c65a..4001fc9dddc 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 fd89c5be2d0..265c6e9dde6 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.const import NotificationSubType 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 @@ from .const import ( MEDIA_UPNP_CLASS_MAP, REPEAT_PLAY_MODES, SHUFFLE_PLAY_MODES, + STREAMABLE_PROTOCOLS, ) from .data import EventListenAddr, get_domain_data @@ -512,7 +519,7 @@ class DlnaDmrEntity(MediaPlayerEntity): 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 @@ class DlnaDmrEntity(MediaPlayerEntity): """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 @@ class DlnaDmrEntity(MediaPlayerEntity): 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 @@ class DlnaDmrEntity(MediaPlayerEntity): 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 3cb4b2a726a..0abda9e1ed3 100644 --- a/tests/components/dlna_dmr/test_media_player.py +++ b/tests/components/dlna_dmr/test_media_player.py @@ -3,6 +3,7 @@ from __future__ import annotations 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 @@ from async_upnp_client.exceptions import ( 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.const import ( 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 d5410bf232c..5706ca19486 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -331,6 +331,7 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator): _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 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator): 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 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator): 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 ba7380bcaa2..ad36fb4e339 100644 --- a/homeassistant/components/cast/helpers.py +++ b/homeassistant/components/cast/helpers.py @@ -81,8 +81,8 @@ class ChromeCastZeroconf: 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 a3b4dea8424..bfbe4f84d60 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 @@ ENTITY_SCHEMA = vol.All( @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 @@ async def async_setup_entry( 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: Callable[[], None] | None = None + self._del_remove_handler: Callable[[], None] | None = None + self._name: str | 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), - ) - - 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 @@ class CastDevice(MediaPlayerEntity): 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 + 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.""" - self._cast_info = cast_info - - 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 @@ class CastDevice(MediaPlayerEntity): 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 @@ class CastDevice(MediaPlayerEntity): 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 @@ class CastDevice(MediaPlayerEntity): 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 @@ class DynamicCastGroup: 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 1c2da93f0a1..2299389f12b 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 1682edb66a3..5a0a7bbf29f 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -12,6 +12,7 @@ from zwave_js_server.model.node import Node as ZwaveNode from zwave_js_server.model.notification import ( EntryControlNotification, NotificationNotification, + PowerLevelNotification, ) from zwave_js_server.model.value import Value, ValueNotification @@ -41,6 +42,7 @@ from homeassistant.helpers.typing import ConfigType 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 @@ from .const import ( 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 @@ async def async_setup_entry( # noqa: C901 @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 @@ async def async_setup_entry( # noqa: C901 ATTR_EVENT_DATA: notification.event_data, } ) - else: + elif isinstance(notification, NotificationNotification): event_data.update( { ATTR_COMMAND_CLASS_NAME: "Notification", @@ -426,6 +432,17 @@ async def async_setup_entry( # noqa: C901 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 2d16c0113c9..bd46497f629 100644 --- a/homeassistant/components/zwave_js/const.py +++ b/homeassistant/components/zwave_js/const.py @@ -56,6 +56,9 @@ ATTR_EVENT_DATA = "event_data" 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 d66cd52be40..32859ae3c37 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 654f8f2ee1b..f596a64e5b1 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 cf95644867b..1b065833adc 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 8796d7556f5..3867c326759 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 6399e9611f2..b37bb1cfd12 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 79e792aa7d8..196095a5b6e 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 3867c326759..a5bd4ad853c 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 b37bb1cfd12..5c70c20bf33 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 543b5116760..01cfff59357 100644 --- a/homeassistant/components/vicare/binary_sensor.py +++ b/homeassistant/components/vicare/binary_sensor.py @@ -205,6 +205,7 @@ class ViCareBinarySensor(BinarySensorEntity): "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 e924a735e0f..e1d6bc4223c 100644 --- a/homeassistant/components/vicare/button.py +++ b/homeassistant/components/vicare/button.py @@ -101,6 +101,7 @@ class ViCareButton(ButtonEntity): "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 64df7260c5b..8320db73ad4 100644 --- a/homeassistant/components/vicare/climate.py +++ b/homeassistant/components/vicare/climate.py @@ -174,6 +174,7 @@ class ViCareClimate(ClimateEntity): "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 6f1b6097c4f..249cadaee86 100644 --- a/homeassistant/components/vicare/sensor.py +++ b/homeassistant/components/vicare/sensor.py @@ -467,6 +467,7 @@ class ViCareSensor(SensorEntity): "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 8a7169333bf..139c8f75e7f 100644 --- a/homeassistant/components/vicare/water_heater.py +++ b/homeassistant/components/vicare/water_heater.py @@ -147,6 +147,7 @@ class ViCareWater(WaterHeaterEntity): "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 1c84c4f5510..970bdd4b3b6 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 @@ from homeassistant.core import HomeAssistant 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 @@ CONFIG_SCHEMA = vol.Schema( 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 00000000000..85e61cd8d9a --- /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 034deb911d1..f3c098a841e 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 @@ from homeassistant.core import HomeAssistant 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 @@ class LaMetricNotificationService(BaseNotificationService): 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 @@ class LaMetricNotificationService(BaseNotificationService): 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 @@ class LaMetricNotificationService(BaseNotificationService): 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 @@ class LaMetricNotificationService(BaseNotificationService): 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 @@ class LaMetricNotificationService(BaseNotificationService): 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 bd46497f629..8f6fada2106 100644 --- a/homeassistant/components/zwave_js/const.py +++ b/homeassistant/components/zwave_js/const.py @@ -69,6 +69,9 @@ ATTR_PREVIOUS_VALUE_RAW = "previous_value_raw" 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 ca9bd7d24a2..07f89388e67 100644 --- a/homeassistant/components/zwave_js/trigger.py +++ b/homeassistant/components/zwave_js/trigger.py @@ -12,10 +12,11 @@ from homeassistant.const import CONF_PLATFORM 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 00000000000..110cd21294f --- /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 7ebdb4f3748..71223c4ef1e 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 33f6205c7b9..5dbeff87a54 100644 --- a/tests/components/zwave_js/test_trigger.py +++ b/tests/components/zwave_js/test_trigger.py @@ -264,6 +264,440 @@ async def test_zwave_js_value_updated(hass, client, lock_schlage_be469, integrat 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 56edf39311c..4d33e819a8f 100644 --- a/homeassistant/components/modbus/__init__.py +++ b/homeassistant/components/modbus/__init__.py @@ -118,7 +118,7 @@ BASE_COMPONENT_SCHEMA = vol.Schema( { 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 74cd2a49861..4f910043d12 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 91327b4e2a2..75d4d6099c9 100644 --- a/tests/components/modbus/conftest.py +++ b/tests/components/modbus/conftest.py @@ -9,7 +9,7 @@ from pymodbus.exceptions import ModbusException 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 7d9ab3e3471..c9beba694e8 100644 --- a/tests/components/modbus/test_init.py +++ b/tests/components/modbus/test_init.py @@ -76,6 +76,7 @@ from homeassistant.const import ( 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 | 31 +- homeassistant/components/github/const.py | 12 - .../components/github/coordinator.py | 237 +++----- .../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, 201 insertions(+), 1144 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 f16a65fb4c0..7cdd28cd6e9 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 ae13e8df959..4ecff6e9648 100644 --- a/homeassistant/components/github/__init__.py +++ b/homeassistant/components/github/__init__.py @@ -13,13 +13,7 @@ from homeassistant.helpers.aiohttp_client import ( ) 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 - ), - } + coordinator = GitHubDataUpdateCoordinator( + hass=hass, + client=client, + repository=repository, + ) - await coordinators["information"].async_config_entry_first_refresh() + await coordinator.async_config_entry_first_refresh() - hass.data[DOMAIN][repository] = coordinators + 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 7a0c471ab03..efe9d7baa5e 100644 --- a/homeassistant/components/github/const.py +++ b/homeassistant/components/github/const.py @@ -3,9 +3,6 @@ from __future__ import annotations 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 @@ DEFAULT_UPDATE_INTERVAL = timedelta(seconds=300) 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 32f0df98e1f..9769c944f16 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 +from .const import DEFAULT_UPDATE_INTERVAL, DOMAIN, LOGGER -CoordinatorKeyType = Literal["information", "release", "issue", "commit"] +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 GitHubBaseDataUpdateCoordinator(DataUpdateCoordinator[T]): - """Base class for GitHub data update coordinators.""" +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 @@ class GitHubBaseDataUpdateCoordinator(DataUpdateCoordinator[T]): 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 @@ class GitHubBaseDataUpdateCoordinator(DataUpdateCoordinator[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 101bf642c91..c2546d636b8 100644 --- a/homeassistant/components/github/diagnostics.py +++ b/homeassistant/components/github/diagnostics.py @@ -13,7 +13,7 @@ from homeassistant.helpers.aiohttp_client import ( ) 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 18aaa43d18d..7fad114a2ae 100644 --- a/homeassistant/components/github/sensor.py +++ b/homeassistant/components/github/sensor.py @@ -19,19 +19,14 @@ from homeassistant.helpers.typing import StateType 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 @@ SENSOR_DESCRIPTIONS: tuple[GitHubSensorEntityDescription, ...] = ( 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 @@ SENSOR_DESCRIPTIONS: tuple[GitHubSensorEntityDescription, ...] = ( 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 @@ SENSOR_DESCRIPTIONS: tuple[GitHubSensorEntityDescription, ...] = ( 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 @@ SENSOR_DESCRIPTIONS: tuple[GitHubSensorEntityDescription, ...] = ( 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 @@ SENSOR_DESCRIPTIONS: tuple[GitHubSensorEntityDescription, ...] = ( 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 a99834f0cbd..a75a8cfaa78 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 c0deeaf51ea..00000000000 --- 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 00000000000..d8fe86f6c95 --- /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 d59f1f5c796..00000000000 --- 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 a42763b18d8..00000000000 --- 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 e69206ae784..00000000000 --- 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 6e5e6e13fa4..80dfaec2445 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 cea3edc6b47..cba787cbc28 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 - async_fire_time_changed(hass, dt.utcnow() + DEFAULT_UPDATE_INTERVAL) - await hass.async_block_till_done() + 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() 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 7fad114a2ae..08e8a49af1f 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 @@ SENSOR_DESCRIPTIONS: tuple[GitHubSensorEntityDescription, ...] = ( 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 a9629ba42f4..10927fc3a06 100644 --- a/homeassistant/components/sensibo/climate.py +++ b/homeassistant/components/sensibo/climate.py @@ -325,12 +325,12 @@ class SensiboClimate(CoordinatorEntity, ClimateEntity): 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 c79fb5b7bb5..bf2c1aca17f 100644 --- a/homeassistant/components/sensibo/coordinator.py +++ b/homeassistant/components/sensibo/coordinator.py @@ -38,7 +38,8 @@ class SensiboDataUpdateCoordinator(DataUpdateCoordinator): 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 4d41a6e3ca0..758dfca4b97 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 a5bd4ad853c..bed3ce9000e 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 5c70c20bf33..4189c4dca8b 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 9769c944f16..ae3c9b5dc87 100644 --- a/homeassistant/components/github/coordinator.py +++ b/homeassistant/components/github/coordinator.py @@ -68,6 +68,18 @@ query ($owner: String!, $repository: String!) { 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 08e8a49af1f..75284b2ccd3 100644 --- a/homeassistant/components/github/sensor.py +++ b/homeassistant/components/github/sensor.py @@ -126,6 +126,15 @@ SENSOR_DESCRIPTIONS: tuple[GitHubSensorEntityDescription, ...] = ( "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 d8fe86f6c95..8fe1b17022a 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 ae3c9b5dc87..10f30bb1006 100644 --- a/homeassistant/components/github/coordinator.py +++ b/homeassistant/components/github/coordinator.py @@ -39,6 +39,17 @@ query ($owner: String!, $repository: String!) { 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 75284b2ccd3..a09e440e2ce 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 @@ SENSOR_DESCRIPTIONS: tuple[GitHubSensorEntityDescription, ...] = ( "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 8fe1b17022a..52b0e6ccfd6 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 0abda9e1ed3..c68a311d2b6 100644 --- a/tests/components/dlna_dmr/test_media_player.py +++ b/tests/components/dlna_dmr/test_media_player.py @@ -823,10 +823,11 @@ async def test_play_media_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): + 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 afba19c82d9..a60a12aba30 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 bed3ce9000e..6d5aa6c24aa 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 4189c4dca8b..749d17373be 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 82b844bbd01..62d138d2123 100644 --- a/homeassistant/components/aftership/sensor.py +++ b/homeassistant/components/aftership/sensor.py @@ -149,10 +149,10 @@ class AfterShipSensor(SensorEntity): 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 @@ class AfterShipSensor(SensorEntity): 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 @@ class AfterShipSensor(SensorEntity): 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 526bf004fd5..2b1e20d645e 100644 --- a/homeassistant/components/pure_energie/config_flow.py +++ b/homeassistant/components/pure_energie/config_flow.py @@ -35,7 +35,7 @@ class PureEnergieFlowHandler(ConfigFlow, domain=DOMAIN): 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 64ada3925f3..fffbfd7c7bb 100644 --- a/homeassistant/components/pure_energie/sensor.py +++ b/homeassistant/components/pure_energie/sensor.py @@ -14,7 +14,6 @@ from homeassistant.components.sensor import ( 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 @@ class PureEnergieSensorEntity(CoordinatorEntity[PureEnergieData], SensorEntity): 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 dddafa3c24b..60894ac09f8 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 3e437f9932f..ef5a376034a 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 6d5aa6c24aa..08c4a43bef9 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 749d17373be..ff80c8a8366 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 c4c444441c2..e22f12c3526 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -107,15 +107,16 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity): 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 ab4bde47d91..adcd68ed50e 100644 --- a/homeassistant/components/plugwise/const.py +++ b/homeassistant/components/plugwise/const.py @@ -25,7 +25,6 @@ PLATFORMS_GATEWAY = [ 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 05ef937c6fd..71ca2af9537 100644 --- a/homeassistant/components/plugwise/gateway.py +++ b/homeassistant/components/plugwise/gateway.py @@ -16,14 +16,7 @@ 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, - 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 b7db3880a5e..4f1417ae018 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 08c4a43bef9..daf0559cde6 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 ff80c8a8366..a79ce5ce6b7 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 925571908c5..65b96074cc0 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 9585c8630af..e9e62b77bb0 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 e65c012da85..fc565ef6040 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 494ff28b118..21d7a888dd5 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 6f4e7124d70..67ab7728d1c 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 79013ab88a4..ce9ec6fc850 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 8ca060f720e..1ddb64a0420 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 0157f30c0f0..b6e939ea466 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 94332a74183..217746da084 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 19640de6aa7..ff13c88c598 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 93c96064d43..28eae33719d 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 c0011b9e240..5ce9391d92a 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 ba3d5b98803..df96ad6e341 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 72fc4ad7cd3..8c40d8bf193 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 fe92f1e0da8..26b40b939f6 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 e77c2da80bd..3918983f746 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 85dc0e7f4fe..7779ac3c5a5 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 68d64178efe..628373910b5 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 6952329f20e..a5a51b76746 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 95f0c45bb6b..d53abc4fc13 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 2185a71ed75..402d8354fdb 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 6e95bc0cb36..645c2a57097 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 ecda52c8c5b..06c0d9e257e 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 3b62ad5e079..e8f147c8d57 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 436a00cbfda..64f4b88cecd 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 b1115d5bf2c..2ac7bbe8ac4 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 1c6e2fd9809..d81c3fb19fd 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 fd575836741..7aa5269fc2b 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 be6eaaee436..d8b3f3c6e1c 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 00000000000..46921dd33c0 --- /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 54b55bb8324..5260b0f7170 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 774739eea7b..ef456c825d0 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 611a3ebce8e..cce92f7794a 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 54b55bb8324..5260b0f7170 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 bab52704f79..118803f915b 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 618e1c4e0d2..07740adf2e1 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 bb29747330e..19d983fd718 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 a3635ae5e2e..fbdb51209a0 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 07f374b8a17..3cb92db229c 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 bab52704f79..118803f915b 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 c76b5ba7252..071014416e3 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 2646eb1883e..e17dece7277 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 57714e8ee95..59687ef8ad6 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 d5a3c1815e1..0b9c8c36f08 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 fa290b1bfdc..df3f20ee792 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 d927982617c..0dd1218ec1a 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 bab52704f79..118803f915b 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 316feac28c6..9c4482a2e00 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 5f30894b381..80e09569cf5 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 404adec7a94..2dbff2ebe2c 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 00000000000..113193e3832 --- /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 00000000000..b0c5d3b814d --- /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 d15111e97b0..81e3d96b7bc 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 00000000000..ff0498a01a1 --- /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 edd1051671c..4271012e41c 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 3ecf4413e11..5f47bf22e5e 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 1edd6c06fc2..d706ff53238 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 07066485015..b29a9f075aa 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 db3aa9c03d3..237fb7e22a4 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 6d28f2d1842..1e6a773c02d 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 2d28d795e04..9a00d825777 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 166b4dc48af..8e6a232db7f 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 c56dbc9bc45..9d11ed63273 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 3974c507fd4..f7a170871ef 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 242b4ae4e6a..b689f65ff96 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 a01a22cda23..b52262e8602 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 eba13432262..59bd52f3b0e 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 f2beb19d5da..767f77e58bd 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 a243e22b566..48c076d8eed 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 00000000000..93c06fc2363 --- /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 00000000000..cb725e87646 --- /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 00000000000..eb5d98c7ebf --- /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 00000000000..d4bd60def2c --- /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 00000000000..9557e4bc08f --- /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 00000000000..457f7cfebc0 --- /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 00000000000..bb7b3fe9f13 --- /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 00000000000..526326fccc1 --- /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 00000000000..8c2a8402124 --- /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 00000000000..7fa7144f914 --- /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 00c0e1ee3bf..a884d21ce4a 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 f301eb5bb8c..607a99c87bf 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 490ed36838d..ce96a2c2ab6 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 3192242fd21..1c490c0ea47 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 18c2f0869bd..bab52704f79 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 026160f21e1..f1d52c417a8 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 1dbdef83eea..34312fd03a1 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 e146efbb749..3f7c4990cd0 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 975011bf88e..912cdfe64aa 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 00000000000..e2a8e761bfc --- /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 c749d7f7def..f6a55f9897c 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 00000000000..6a74424c888 --- /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 00000000000..6b8cd4ac642 --- /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 00000000000..153aa4126b0 --- /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 18c2f0869bd..bab52704f79 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 de1c6594003..27721f76d80 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 f4add9bb240..3b724d830f2 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 90dc6fee033..4b3af37a706 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 2a1f19d55fc..f9bbee69757 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 78b2a8f8088..205f1ea320d 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 65a346ccfbf..3c5f96d7002 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 df42b440048..f325bdfa640 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 180acc83776..9909277deed 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 a7d7fec3633..c370d68d126 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 1cb4117e799..0b1641b60ec 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 d0552cb6d33..adc306feae4 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 009b8f62245..8b9f26b27cd 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 d625d4504c3..7dad02bdf7d 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 3a3088f51f5..c8e9954660d 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 e332f005aa5..32ac668aa29 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 ec9ff8394b6..d564f7d754f 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 c1d7d1676a9..87024302a45 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 dd48cf243c2..2665de72b15 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 1e0dbb1f7dd..bc06bff8053 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 00000000000..3f6b1f68dc5 --- /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 7f545aa35b4..fdad6c16b61 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 13dac9bdf42..4266ec99ffb 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 663137ffa49..4d431a31479 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 f55937b4ec7..eab23bbd0ff 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 00000000000..39d25423fab --- /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 e39864a6157..119443962fc 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 @@ MOCK_PIN_CONFIG = {CONF_PIN: PIN} 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 @@ MOCK_OPTIONS = { 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 @@ MOCK_IMPORT_VALID_TV_CONFIG = { 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 @@ MOCK_TV_WITH_INCLUDE_CONFIG = { 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 @@ MOCK_TV_WITH_EXCLUDE_CONFIG = { 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 @@ MOCK_TV_WITH_ADDITIONAL_APPS_CONFIG = { 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 @@ MOCK_SPEAKER_APPS_FAILURE = { 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 @@ MOCK_TV_APPS_FAILURE = { 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 @@ MOCK_TV_APPS_WITH_VALID_APPS_CONFIG = { 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 817f23d52c5..3250163ef8e 100644 --- a/tests/components/vizio/test_config_flow.py +++ b/tests/components/vizio/test_config_flow.py @@ -5,7 +5,7 @@ import pytest 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 80f72280951..1c637733927 100644 --- a/tests/components/vizio/test_media_player.py +++ b/tests/components/vizio/test_media_player.py @@ -24,8 +24,6 @@ from homeassistant.components.media_player import ( 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 @@ from homeassistant.components.media_player import ( 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 515e5c0de96..45be07585d7 100644 --- a/homeassistant/components/samsungtv/__init__.py +++ b/homeassistant/components/samsungtv/__init__.py @@ -24,7 +24,6 @@ from homeassistant.core import Event, HomeAssistant, callback 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 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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 421b88d50ad..db99726f20c 100644 --- a/homeassistant/components/samsungtv/media_player.py +++ b/homeassistant/components/samsungtv/media_player.py @@ -164,7 +164,7 @@ class SamsungTVDevice(MediaPlayerEntity): 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 f37fa1c3da2..292b194eea1 100644 --- a/homeassistant/components/version/config_flow.py +++ b/homeassistant/components/version/config_flow.py @@ -26,7 +26,7 @@ from .const import ( 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 9ee556c6b7f..9f480c25cc5 100644 --- a/homeassistant/components/version/const.py +++ b/homeassistant/components/version/const.py @@ -30,7 +30,7 @@ ATTR_CHANNEL: Final = CONF_CHANNEL 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 e5bf9e9b93d..c3d20fb0f16 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 686fffec252..e811d43d502 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 57c837178a4..af04ee032dc 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 @@ class ConfigEntry: # 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 @@ class ConfigEntry: 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 c8dfb3ed841..bdebc7217a7 100644 --- a/tests/common.py +++ b/tests/common.py @@ -583,6 +583,7 @@ class MockModule: 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 @@ class MockModule: 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 6608bf3471d..cfc6d8d4907 100644 --- a/tests/components/config/test_config_entries.py +++ b/tests/components/config/test_config_entries.py @@ -12,7 +12,7 @@ from homeassistant.components.config import config_entries 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 @@ async def test_get_entries(hass, client): "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 @@ async def test_get_entries(hass, client): "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 @@ async def test_get_entries(hass, client): "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 test_create_account(hass, client, enable_custom_integrations): "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 test_two_step_flow(hass, client, enable_custom_integrations): "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 f43f9a4d8ce..f923b326100 100644 --- a/tests/components/config/test_device_registry.py +++ b/tests/components/config/test_device_registry.py @@ -3,8 +3,14 @@ import pytest 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 ee5e44f626c..83df8952278 100644 --- a/homeassistant/components/samsungtv/bridge.py +++ b/homeassistant/components/samsungtv/bridge.py @@ -330,7 +330,7 @@ class SamsungTVWSBridge(SamsungTVBridge): 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 @@ class SamsungTVWSBridge(SamsungTVBridge): 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 b4aa3137482..ef0e99001c9 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 daf0559cde6..e90f95a409a 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 a79ce5ce6b7..86ce8921fd6 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 b08b2381211..6e1151cdccb 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 e90f95a409a..e47d2aa18f8 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 86ce8921fd6..af949898181 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 0f4bef2b38f..4640d062ac7 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 e47d2aa18f8..132ece5a55f 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 af949898181..a7881f1b8c3 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 f5eff45539e..9bc952a21ea 100644 --- a/homeassistant/components/motion_blinds/cover.py +++ b/homeassistant/components/motion_blinds/cover.py @@ -51,6 +51,7 @@ TILT_DEVICE_MAP = { 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 47efe9bf18e..c904320d9af 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 132ece5a55f..2f1367834a2 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 a7881f1b8c3..564fe93158e 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 021b986f82e..104f8db5ba0 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 eed9bd92803..f7d827534b3 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 .const import DOMAIN 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 2f1367834a2..bec28e688e7 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 564fe93158e..5787a59ea57 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 ca4d0460173..cb23b3eef34 100644 --- a/tests/components/wiz/__init__.py +++ b/tests/components/wiz/__init__.py @@ -7,7 +7,7 @@ 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.bulblibrary import BulbClass, Features, KelvinRange from pywizlight.discovery import DiscoveredBulb from homeassistant.components.wiz.const import DOMAIN @@ -84,10 +84,23 @@ REAL_BULB_CONFIG = json.loads( "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_RGBWW_BULB = BulbType( 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_RGBW_BULB = BulbType( 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_DIMMABLE_BULB = BulbType( 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_TURNABLE_BULB = BulbType( 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 @@ def _mocked_wizlight(device, extended_white_range, bulb_type) -> wizlight: 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 a1ab5e6bbae..1d45be9b8cf 100644 --- a/tests/components/wiz/test_number.py +++ b/tests/components/wiz/test_number.py @@ -6,12 +6,17 @@ 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 +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 69bf65dd8a4..00000000000 --- 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 36a8cd24213..cbab107994b 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -34,6 +34,7 @@ from homeassistant.const import ( POWER_WATT, PRESSURE_HPA, TEMP_CELSIUS, + TIME_DAYS, TIME_HOURS, TIME_SECONDS, VOLUME_CUBIC_METERS, @@ -93,9 +94,15 @@ ATTR_AQI = "aqi" 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_MODE = "night_mode" 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 @@ SENSOR_TYPES = { 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 @@ SENSOR_TYPES = { 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 @@ SENSOR_TYPES = { 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 @@ AIRFRESH_SENSORS = ( ) 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 66929fff79c..e22e06df7e2 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -120,10 +120,8 @@ CONFIG_SCHEMA = vol.Schema( 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 5f6dd4e61aa..1fe64defe36 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 00000000000..92b2848caef --- /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 104f8db5ba0..2dacb32c094 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 8809dd45f4a..60a8e50c523 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -165,5 +165,8 @@ DHCP: list[dict[str, str | bool]] = [ {'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 5e4b18eebca..7874fb87b00 100644 --- a/homeassistant/components/fritz/sensor.py +++ b/homeassistant/components/fritz/sensor.py @@ -5,7 +5,7 @@ from collections.abc import Callable 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 @@ SENSOR_TYPES: tuple[FritzSensorEntityDescription, ...] = ( 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 @@ SENSOR_TYPES: tuple[FritzSensorEntityDescription, ...] = ( 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 @@ SENSOR_TYPES: tuple[FritzSensorEntityDescription, ...] = ( 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 @@ SENSOR_TYPES: tuple[FritzSensorEntityDescription, ...] = ( 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 @@ SENSOR_TYPES: tuple[FritzSensorEntityDescription, ...] = ( 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 @@ SENSOR_TYPES: tuple[FritzSensorEntityDescription, ...] = ( 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 @@ SENSOR_TYPES: tuple[FritzSensorEntityDescription, ...] = ( 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 da4bdaede00..1b48888ac41 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 d80ca44f894..a8d24fa03a3 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 0d041463d11..b277bd97edf 100644 --- a/homeassistant/components/onboarding/views.py +++ b/homeassistant/components/onboarding/views.py @@ -188,9 +188,8 @@ class CoreConfigOnboardingView(_BaseOnboardingView): 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 @@ class CoreConfigOnboardingView(_BaseOnboardingView): 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 00000000000..89c2f220159 --- /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 00000000000..1c6964d0715 --- /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 00000000000..eb456db08e8 --- /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 00000000000..865d8b25ab1 --- /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 00000000000..952b0e67e25 --- /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 00000000000..7bf9bc9ca66 --- /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 00000000000..5f89dd9447c --- /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 aa773be82f0..ddec2deb65e 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -261,6 +261,7 @@ FLOWS = [ "pvoutput", "pvpc_hourly_pricing", "rachio", + "radio_browser", "rainforest_eagle", "rainmachine", "rdw", diff --git a/requirements_all.txt b/requirements_all.txt index bec28e688e7..cca10c6e92e 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 5787a59ea57..602c6c3c2cb 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 9605fb9e71c..976e2b84c68 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 00000000000..708e07fefe6 --- /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 00000000000..5a5b888d944 --- /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 00000000000..8a5a3d9ccce --- /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 c3a0b43f7de..3f40668fdd5 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 cca10c6e92e..c6c0267de34 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 602c6c3c2cb..f82e28776c0 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 832631878eb..fb72ef8b1db 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 56b13ce90a7..6e0cc30e207 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 4b64e028f97..0b7e355ef24 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 58c9b414d5a..8304b093a14 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 d7a9ed29948..0def2e493c2 100644 --- a/homeassistant/components/nest/camera_sdm.py +++ b/homeassistant/components/nest/camera_sdm.py @@ -4,6 +4,7 @@ from __future__ import annotations import asyncio from collections.abc import Callable import datetime +import functools import logging from pathlib import Path @@ -72,7 +73,6 @@ class NestCamera(Camera): 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 @@ class NestCamera(Camera): 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 823d37267d6..451ec6d5f8b 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 beeaa21bf27..f9422d60669 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 03dc98f9799..cafdcc6a54f 100644 --- a/tests/components/tplink/test_switch.py +++ b/tests/components/tplink/test_switch.py @@ -4,6 +4,7 @@ from datetime import timedelta 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.const import ATTR_ENTITY_ID, STATE_ON, STATE_UNAVAILABLE 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 7eca4ae4c66..ed7b3d6b293 100644 --- a/homeassistant/components/homekit/const.py +++ b/homeassistant/components/homekit/const.py @@ -215,6 +215,7 @@ CHAR_SWING_MODE = "SwingMode" 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 54d6424e8d5..82d254cb9a5 100644 --- a/homeassistant/components/homekit/type_fans.py +++ b/homeassistant/components/homekit/type_fans.py @@ -39,6 +39,7 @@ from .const import ( 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 @@ class Fan(HomeAccessory): 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 @@ class Fan(HomeAccessory): ), ) - 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 @@ class Fan(HomeAccessory): # 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 @@ class Fan(HomeAccessory): """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 @@ class Fan(HomeAccessory): 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 @@ class Fan(HomeAccessory): # 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 c1ce1ffaddb..b520eb7f874 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 9564f2b662f..419f5bc50ff 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 homeassistant.const import CONF_DEVICE - -CONF_DATA = {CONF_DEVICE: DEFAULT_PORT} - -IMPORT_DATA = {"sensor": {"platform": "modem_callerid"}} +from serial.tools.list_ports_common import ListPortInfo -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 0956a8fe1b7..19a98106c63 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 @@ DISCOVERY_INFO = usb.UsbServiceInfo( 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 f467ca5af51..0465fb24a07 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 9b6b7c6b6c6..ce723e84b8c 100644 --- a/homeassistant/components/rflink/binary_sensor.py +++ b/homeassistant/components/rflink/binary_sensor.py @@ -13,11 +13,13 @@ from homeassistant.const import ( 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 @@ class RflinkBinarySensor(RflinkDevice, BinarySensorEntity): 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 4b6acfb4394..00873e9d176 100644 --- a/tests/components/rflink/test_binary_sensor.py +++ b/tests/components/rflink/test_binary_sensor.py @@ -15,10 +15,10 @@ from homeassistant.const import ( 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 @@ async def test_off_delay(hass, legacy_patchable_time, monkeypatch): 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 02443180358..d190a2e96ea 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 8a75615e617..46d142fc962 100644 --- a/homeassistant/components/filter/sensor.py +++ b/homeassistant/components/filter/sensor.py @@ -3,7 +3,7 @@ from __future__ import annotations 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 @@ from homeassistant.components.sensor import ( DEVICE_CLASSES as SENSOR_DEVICE_CLASSES, DOMAIN as SENSOR_DOMAIN, PLATFORM_SCHEMA, + SensorDeviceClass, SensorEntity, ) from homeassistant.const import ( @@ -346,6 +347,9 @@ class SensorFilter(SensorEntity): @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 c2fc8cbdd06..b42fc3fa9fe 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 d5e1671c135..76598eac963 100644 --- a/homeassistant/components/media_source/local_source.py +++ b/homeassistant/components/media_source/local_source.py @@ -10,7 +10,7 @@ from aiohttp import web 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 @@ class LocalSource(MediaSource): return media -class LocalMediaView(HomeAssistantView): +class LocalMediaView(http.HomeAssistantView): """ Local Media Finder View. @@ -231,7 +232,7 @@ class LocalMediaView(HomeAssistantView): 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 @@ class UploadMediaView(HomeAssistantView): 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 f9ee560620c..de36566fb56 100644 --- a/tests/components/media_source/test_local_source.py +++ b/tests/components/media_source/test_local_source.py @@ -7,7 +7,7 @@ from unittest.mock import patch 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 @@ async def test_upload_view(hass, hass_client, temp_dir, hass_admin_user): 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 615e86ae920..0727fa81e44 100644 --- a/homeassistant/components/modbus/base_platform.py +++ b/homeassistant/components/modbus/base_platform.py @@ -312,7 +312,7 @@ class BaseSwitch(BasePlatform, ToggleEntity, RestoreEntity): 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 1b48888ac41..654e1a3e4d7 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 4d33e819a8f..a5ad05a4711 100644 --- a/homeassistant/components/modbus/__init__.py +++ b/homeassistant/components/modbus/__init__.py @@ -74,6 +74,7 @@ from .const import ( 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 @@ BINARY_SENSOR_SCHEMA = BASE_COMPONENT_SCHEMA.extend( 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 07756b0f207..f65d0ad0348 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 @@ class ModbusBinarySensor(BasePlatform, RestoreEntity, BinarySensorEntity): 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 @@ class ModbusBinarySensor(BasePlatform, RestoreEntity, BinarySensorEntity): 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 d4f0fa6d9ea..934d14012f8 100644 --- a/homeassistant/components/modbus/const.py +++ b/homeassistant/components/modbus/const.py @@ -37,6 +37,7 @@ CONF_RETRY_ON_EMPTY = "retry_on_empty" 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 5127bd55ad1..fbe0003b78a 100644 --- a/tests/components/modbus/test_binary_sensor.py +++ b/tests/components/modbus/test_binary_sensor.py @@ -7,6 +7,7 @@ from homeassistant.components.modbus.const import ( 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 c3d20fb0f16..07bdc794128 100644 --- a/homeassistant/components/config/config_entries.py +++ b/homeassistant/components/config/config_entries.py @@ -88,15 +88,17 @@ class ConfigManagerEntryResourceReloadView(HomeAssistantView): 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 cfc6d8d4907..06b9f1ae7f6 100644 --- a/tests/components/config/test_config_entries.py +++ b/tests/components/config/test_config_entries.py @@ -9,7 +9,7 @@ import voluptuous as vol 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 62a73036238..62ff8187731 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 fd69ab678a7..1dc6cf2caa1 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 f59b9b9c7cb..2f9966afc29 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 924e88793e4..7d86502784c 100644 --- a/homeassistant/components/wiz/config_flow.py +++ b/homeassistant/components/wiz/config_flow.py @@ -71,6 +71,12 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 82f19a61002..9b22d35de7d 100644 --- a/homeassistant/components/wiz/entity.py +++ b/homeassistant/components/wiz/entity.py @@ -6,6 +6,7 @@ from typing import Any 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 @@ class WizEntity(CoordinatorEntity, Entity): 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 2dacb32c094..df780d7b39c 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 c6c0267de34..1d3e91cd65d 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 f82e28776c0..1ad42575a89 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 cb23b3eef34..c4a31b0a394 100644 --- a/tests/components/wiz/__init__.py +++ b/tests/components/wiz/__init__.py @@ -150,6 +150,17 @@ FAKE_SOCKET = BulbType( 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 c79cf74e130..48166e941d4 100644 --- a/tests/components/wiz/test_light.py +++ b/tests/components/wiz/test_light.py @@ -22,6 +22,7 @@ from homeassistant.helpers import entity_registry as er 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 d57681ae504..b172b5468b0 100644 --- a/homeassistant/components/plugwise/entity.py +++ b/homeassistant/components/plugwise/entity.py @@ -4,6 +4,10 @@ from __future__ import annotations 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 @@ class PlugwiseEntity(CoordinatorEntity[PlugwiseData]): 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 534e5f5dd02..4eaa75f92ae 100644 --- a/homeassistant/components/sonos/binary_sensor.py +++ b/homeassistant/components/sonos/binary_sensor.py @@ -16,6 +16,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback 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 @@ class SonosMicrophoneSensorEntity(SonosEntity, BinarySensorEntity): 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 16ca58448e2..018eab4ca04 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -263,6 +263,10 @@ class SonosSpeaker: ) 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 bc49c12ed81..70061e88692 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 bda4e08cd25..8a51ea5b2e6 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 f788d12747d..f35941d6773 100644 --- a/homeassistant/components/overkiz/config_flow.py +++ b/homeassistant/components/overkiz/config_flow.py @@ -65,6 +65,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 46d142fc962..d2ad3ec313c 100644 --- a/homeassistant/components/filter/sensor.py +++ b/homeassistant/components/filter/sensor.py @@ -29,6 +29,7 @@ from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, CONF_ENTITY_ID, CONF_NAME, + CONF_UNIQUE_ID, STATE_UNAVAILABLE, STATE_UNKNOWN, ) @@ -150,6 +151,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( 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 b42fc3fa9fe..b044b2c08fa 100644 --- a/tests/components/filter/test_sensor.py +++ b/tests/components/filter/test_sensor.py @@ -26,6 +26,7 @@ from homeassistant.const import ( 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 3d96624e628..a24c9be4e6d 100644 --- a/homeassistant/components/renault/binary_sensor.py +++ b/homeassistant/components/renault/binary_sensor.py @@ -79,29 +79,61 @@ class RenaultBinarySensor( 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 462c5bbc239..c4e42a7be5b 100644 --- a/homeassistant/components/renault/renault_vehicle.py +++ b/homeassistant/components/renault/renault_vehicle.py @@ -148,4 +148,9 @@ COORDINATORS: tuple[RenaultCoordinatorDescription, ...] = ( 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 a1f3b42167c..da86b41e3b0 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 90a1665b75b..d0fadc54030 100644 --- a/tests/components/renault/const.py +++ b/tests/components/renault/const.py @@ -252,6 +252,7 @@ MOCK_VEHICLES = { True, # location True, # battery-status True, # charge-mode + True, # lock-status ], "endpoints": { "battery_status": "battery_status_not_charging.json", @@ -259,6 +260,7 @@ MOCK_VEHICLES = { "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 @@ MOCK_VEHICLES = { 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 @@ MOCK_VEHICLES = { 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 @@ MOCK_VEHICLES = { 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 @@ MOCK_VEHICLES = { 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 00000000000..9cda30d5a62 --- /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 26747785837..a266998a9b5 100644 --- a/homeassistant/components/fronius/sensor.py +++ b/homeassistant/components/fronius/sensor.py @@ -24,6 +24,7 @@ from homeassistant.const import ( FREQUENCY_HERTZ, PERCENTAGE, POWER_VOLT_AMPERE, + POWER_VOLT_AMPERE_REACTIVE, POWER_WATT, TEMP_CELSIUS, ) @@ -52,7 +53,6 @@ _LOGGER: Final = logging.getLogger(__name__) 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 @@ METER_ENTITY_DESCRIPTIONS: list[SensorEntityDescription] = [ 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 @@ METER_ENTITY_DESCRIPTIONS: list[SensorEntityDescription] = [ 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 @@ METER_ENTITY_DESCRIPTIONS: list[SensorEntityDescription] = [ 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 @@ METER_ENTITY_DESCRIPTIONS: list[SensorEntityDescription] = [ 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 @@ METER_ENTITY_DESCRIPTIONS: list[SensorEntityDescription] = [ 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 @@ METER_ENTITY_DESCRIPTIONS: list[SensorEntityDescription] = [ 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 @@ METER_ENTITY_DESCRIPTIONS: list[SensorEntityDescription] = [ 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 @@ METER_ENTITY_DESCRIPTIONS: list[SensorEntityDescription] = [ 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 654e1a3e4d7..7bee7669e16 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 e6d5bebff42..683a403cb08 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 00000000000..eff953592ac --- /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 7ad11809e6a..833441442b2 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 1d3e91cd65d..52eccbb1271 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 1ad42575a89..f7c226536e2 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 d60a8aabb1a..b29079affcf 100644 --- a/homeassistant/components/shelly/__init__.py +++ b/homeassistant/components/shelly/__init__.py @@ -681,19 +681,17 @@ class RpcDeviceWrapper(update_coordinator.DataUpdateCoordinator): 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 | 131 ++++++++--- 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, 359 insertions(+), 57 deletions(-) diff --git a/homeassistant/components/emulated_kasa/manifest.json b/homeassistant/components/emulated_kasa/manifest.json index 8506ad75e3f..c11fecb3ff1 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 edc5cd0823e..aaf3630ae19 100644 --- a/homeassistant/components/sense/__init__.py +++ b/homeassistant/components/sense/__init__.py @@ -3,18 +3,21 @@ import asyncio 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 6bd33291d7f..eea36424662 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 @@ DATA_SCHEMA = vol.Schema( ) -async def validate_input(hass: core.HomeAssistant, data): - """Validate the user input allows us to connect. - - Data has the keys from DATA_SCHEMA with values provided by the user. - """ - timeout = data[CONF_TIMEOUT] - client_session = async_get_clientsession(hass) - - 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]) - - # Return info that you want to store in the config entry. - return {"title": data[CONF_EMAIL]} - - class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow for Sense.""" VERSION = 1 - async def async_step_user(self, user_input=None): - """Handle the initial step.""" + def __init__(self): + """Init Config .""" + self._gateway = None + self._auth_data = {} + super().__init__() + + 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) + + 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] + ) + + 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 + ) + + 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 a7ec66d8b83..30de722a7bc 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 29e85c98fc2..a519155bee1 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 24cde7411a8..fd9a7ade4bf 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 52eccbb1271..93c5a9072ac 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 f7c226536e2..723daaca0cd 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 0058c05bf80..f939142aee4 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 + +MOCK_CONFIG = { + "timeout": 6, + "email": "test-email", + "password": "test-password", + "access_token": "ABC", + "user_id": "123", + "monitor_id": "456", +} -async def test_form(hass): +@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 | 45 ++++--- tests/components/picnic/test_sensor.py | 112 ++++++++++++++---- 3 files changed, 164 insertions(+), 62 deletions(-) diff --git a/homeassistant/components/picnic/const.py b/homeassistant/components/picnic/const.py index a97d46e0ad0..f33f58c0eb9 100644 --- a/homeassistant/components/picnic/const.py +++ b/homeassistant/components/picnic/const.py @@ -22,6 +22,7 @@ ATTRIBUTION = "Data provided by Picnic" 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_SELECTED_SLOT_MIN_ORDER_VALUE = "selected_slot_min_order_value" 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 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = ( 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 @@ SENSOR_TYPES: tuple[PicnicSensorEntityDescription, ...] = ( 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 24f3086134f..773142a0109 100644 --- a/homeassistant/components/picnic/coordinator.py +++ b/homeassistant/components/picnic/coordinator.py @@ -13,7 +13,7 @@ from homeassistant.core import HomeAssistant, callback 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 @@ class PicnicUpdateCoordinator(DataUpdateCoordinator): 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 @@ class PicnicUpdateCoordinator(DataUpdateCoordinator): 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 - 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 + # Make sure delivery_time is a dict + last_order.setdefault("delivery_time", {}) + + 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 a4a52e50453..7b1bdeb1d12 100644 --- a/tests/components/picnic/test_sensor.py +++ b/tests/components/picnic/test_sensor.py @@ -238,16 +238,6 @@ class TestPicnicSensor(unittest.IsolatedAsyncioTestCase): cls=SensorDeviceClass.TIMESTAMP, ) 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", - cls=SensorDeviceClass.TIMESTAMP, - ) - self._assert_sensor( - "sensor.picnic_last_order_eta_end", - "2021-02-26T20:14:00+00:00", - cls=SensorDeviceClass.TIMESTAMP, - ) self._assert_sensor( "sensor.picnic_last_order_max_order_time", "2021-02-25T21:00:00+00:00", @@ -261,6 +251,26 @@ class TestPicnicSensor(unittest.IsolatedAsyncioTestCase): self._assert_sensor( "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_next_delivery_eta_end", + "unknown", + cls=SensorDeviceClass.TIMESTAMP, + ) + self._assert_sensor( + "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): """Test that some sensors are disabled by default.""" @@ -271,6 +281,8 @@ class TestPicnicSensor(unittest.IsolatedAsyncioTestCase): 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 @@ class TestPicnicSensor(unittest.IsolatedAsyncioTestCase): "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 @@ class TestPicnicSensor(unittest.IsolatedAsyncioTestCase): # 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 @@ class TestPicnicSensor(unittest.IsolatedAsyncioTestCase): } 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 @@ class TestPicnicSensor(unittest.IsolatedAsyncioTestCase): # 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 @@ class TestPicnicSensor(unittest.IsolatedAsyncioTestCase): 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 @@ class TestPicnicSensor(unittest.IsolatedAsyncioTestCase): 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 @@ class TestPicnicSensor(unittest.IsolatedAsyncioTestCase): # 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 @@ class TestPicnicSensor(unittest.IsolatedAsyncioTestCase): # 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 f65d0ad0348..50281bd2b29 100644 --- a/homeassistant/components/modbus/binary_sensor.py +++ b/homeassistant/components/modbus/binary_sensor.py @@ -126,7 +126,7 @@ class SlaveSensor(CoordinatorEntity, RestoreEntity, BinarySensorEntity): ) -> 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 75d4d6099c9..b00f9700f7f 100644 --- a/tests/components/modbus/conftest.py +++ b/tests/components/modbus/conftest.py @@ -16,7 +16,7 @@ import homeassistant.util.dt as dt_util 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 fbe0003b78a..15a03b76927 100644 --- a/tests/components/modbus/test_binary_sensor.py +++ b/tests/components/modbus/test_binary_sensor.py @@ -25,7 +25,7 @@ from homeassistant.core import State 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 db471709c10..80c8590f7cc 100644 --- a/tests/components/modbus/test_climate.py +++ b/tests/components/modbus/test_climate.py @@ -22,7 +22,7 @@ from homeassistant.core import State 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 cc879b2c168..6797dc8713c 100644 --- a/tests/components/modbus/test_cover.py +++ b/tests/components/modbus/test_cover.py @@ -32,8 +32,8 @@ from homeassistant.core import State 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 f766f816109..9b0564504d9 100644 --- a/tests/components/modbus/test_fan.py +++ b/tests/components/modbus/test_fan.py @@ -32,8 +32,8 @@ from homeassistant.core import State 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 c9beba694e8..9bd0a2caa6d 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 220924733ad..f98f1105fa0 100644 --- a/tests/components/modbus/test_light.py +++ b/tests/components/modbus/test_light.py @@ -32,8 +32,8 @@ from homeassistant.core import State 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 0edd9bdc945..053dc46c6ba 100644 --- a/tests/components/modbus/test_sensor.py +++ b/tests/components/modbus/test_sensor.py @@ -37,7 +37,7 @@ from homeassistant.core import State 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 86a26e18742..006e7ee8d15 100644 --- a/tests/components/modbus/test_switch.py +++ b/tests/components/modbus/test_switch.py @@ -40,8 +40,8 @@ from .conftest import TEST_ENTITY_NAME, ReadResult, do_next_cycle 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 c98dbbd270a..ddbced5286d 100644 --- a/homeassistant/components/mqtt/abbreviations.py +++ b/homeassistant/components/mqtt/abbreviations.py @@ -16,6 +16,7 @@ ABBREVIATIONS = { "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 @@ ABBREVIATIONS = { "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 d5665a1beff..09b23029b0e 100644 --- a/homeassistant/components/mqtt/light/schema_basic.py +++ b/homeassistant/components/mqtt/light/schema_basic.py @@ -68,6 +68,7 @@ from .schema import MQTT_LIGHT_SCHEMA_SCHEMA _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_TEMPLATE = "color_temp_command_template" 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 @@ DEFAULT_ON_COMMAND_TYPE = "last" 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 @@ VALUE_TEMPLATE_KEYS = [ _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 @@ _PLATFORM_SCHEMA_BASE = ( 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 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): ) # 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 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): 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 782ee40f0c8..d6125729191 100644 --- a/tests/components/mqtt/test_light.py +++ b/tests/components/mqtt/test_light.py @@ -152,6 +152,37 @@ light: 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 a8d24fa03a3..075c8abbc65 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 00000000000..b09547e07c8 --- /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 00000000000..7ae3a104fc1 --- /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 00000000000..8c260272d5f --- /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 00000000000..d3a65448f84 --- /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 00000000000..feee4b6e903 --- /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 00000000000..84910b7ff67 --- /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 00000000000..9b59960a78a --- /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 00000000000..6d07a25a27d --- /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 ddec2deb65e..5530c73ed50 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -72,6 +72,7 @@ FLOWS = [ "dialogflow", "directv", "dlna_dmr", + "dlna_dms", "dnsip", "doorbird", "dsmr", diff --git a/homeassistant/generated/ssdp.py b/homeassistant/generated/ssdp.py index 1a243d954b9..f0117e2a9c2 100644 --- a/homeassistant/generated/ssdp.py +++ b/homeassistant/generated/ssdp.py @@ -97,6 +97,24 @@ SSDP = { "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 93c5a9072ac..511f8abb7c5 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 723daaca0cd..926d5e0d6ee 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 00000000000..ed538810521 --- /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 00000000000..6764001be31 --- /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 00000000000..df8d55dbc25 --- /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 00000000000..a0cfb3ab2d2 --- /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 00000000000..4ee9cce91ba --- /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 00000000000..16254adca89 --- /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 00000000000..4b43402ecbd --- /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 fcda7227945..aeb9e59e286 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 uuid import UUID 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 @@ class ValloxState: 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 aed87e9239d..71b0750e2f2 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 44dfb56fafc..eece054c82e 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.core import HomeAssistant 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 @@ SENSORS: tuple[ValloxSensorEntityDescription, ...] = ( 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 511f8abb7c5..ef0210e3067 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 926d5e0d6ee..fe0f3a638bf 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 00000000000..e7ea6ee6d6e --- /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 00000000000..bd8ecbea905 --- /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 9f8043ebd85..b11c3cb6dbe 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 74756efbf38..4f7a23e1d6f 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 314fa994167..9ce98dbfca3 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 328bfae9220..ead0eb7e4f1 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 7d0f716a1c7..37b242811ba 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 146c37f16d1..b49125616cf 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 5e66d06c6ca..757fd41be70 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 84197d3ef01..b9990eb1c04 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 ce9ec6fc850..adb944a4259 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 1ddb64a0420..caaaafc9e9a 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 63bf11b8c07..96ead315392 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 7a94a9c6dfa..8790c6edab4 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 997a547e34f..bb4268a3ffd 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 7c3b0b6737c..4923b2eb0cd 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 716eb30c4a4..ac3a8efd757 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 a6db38ee103..3576b6cd852 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 f77e38ce6fa..c2313d646f6 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 1339995b647..0b69fc40b3e 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 8984314349c..ea3b9dcb95b 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 147d0f2e864..67db15ff170 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 d446d618746..11a61899b84 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 214605b1aa9..bc639192d86 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 51d7e3a7308..417f896375f 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 c704dd81b7f..ea926f10bd2 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 073676503eb..f3f47f18450 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 1ee7a68fa87..a8516ec8041 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 491ef12d920..c93ed8f34e2 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 eec0a7cb383..834c5794861 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 217746da084..0b78eacb826 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 c1ca5b7717a..21da52666bc 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 dde9b024ecd..0acefe23c02 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 79f3f80eca4..09ad8671329 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 5f8926f1a51..55197510f35 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 5ce9391d92a..a68ac09a3fd 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 df96ad6e341..e85920108d3 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 e2450646ef8..4c27c1e2966 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 6688159ee93..1cfe13d694b 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 1dc6cf2caa1..1d4f30fef52 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 efe7d6da3a7..5c81e8942e4 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 504b9a4dd48..7afbcaf616f 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 2e052970d63..779830e0961 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 14320019471..c6c2f597f1d 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 430cd76e408..690d2bf0539 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 8c40d8bf193..bb20b0a61a2 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 4a6d26cd73d..8dad035dc3f 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 e36f0621cdc..f9b5f4ad3d0 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 99acd65a994..489bf0a7c36 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 e0dc11ae470..6db366dae32 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 95124984c3d..f8a27353a6a 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 26b40b939f6..a7a676b4ed9 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 3918983f746..b3d9cbb2bfa 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 b96d49b8615..387ba0d7460 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 7a6ecd18b9f..b3e45927b9b 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 a130ea4b90b..c731fb0834a 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 7779ac3c5a5..1f9956a75fa 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 b8f14483174..44159809e3c 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 7defcb9708e..cab4466b293 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 13a43362f4a..196aaad8643 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 0ffffeb9f04..0c11a8b2d56 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 f7ef9b48449..eacc8de5fd7 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 1dcade479c6..e02c8e3a97b 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 ad0e081b16f..4c3541e2640 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 2deebf72442..81cfbd9ad39 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 c4113f251c1..cd7b4866aae 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 b1bef5448b9..34afbe9df01 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 8bfb96a65bb..180cacc2459 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 768d7dce018..b79dfee9512 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 628373910b5..45a54b59b00 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 6097753fb1f..29ca3b114ca 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 c0695ac82b0..b2d36ed6235 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 7bdf0e12aa7..49c7f5aa0bc 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 72b454e4e0b..91a1d353bcc 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 00000000000..125a31fe9b5 --- /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 64aae4a89f2..ffbf523e7d8 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 212db9b3f2f..77f2ad8910d 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 92b697b7f00..96ad16ac67f 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 9745cd934ed..1854a65a708 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 2596a9c81a1..a28aef55374 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 402d8354fdb..b68034484d3 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 896c5b50791..f1206afd71b 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 57300096d97..5f6b487c58d 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 645c2a57097..9e7fe27ce72 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 a32716878da..916ab585935 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 64f4b88cecd..7a5ed2b1482 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 a8d3fa8e922..7815ff977ef 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 dfbcfc4711f..1c1a8d1e260 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 caa16f5f8a5..e2adc95e3ae 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 ebb51349049..8cb9d4c62b6 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 85731fbc8cf..fbc101807eb 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 2ac7bbe8ac4..7c0975a66f4 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 d81c3fb19fd..1b9fd46f126 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 7aa5269fc2b..245366d355f 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 9a0c110347f..467a98a49ad 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 00000000000..eb707f48797 --- /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 47ae0dc726a..29e798361ff 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 cb6a9ccddb2..fccba780671 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 6dd1e8d2834..7a237b4bf1c 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 57a48c4bb80..122721cfc8c 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 5260b0f7170..877622243c8 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 1c271c60797..a15da4ed961 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 1707156c71a..972611ff6b1 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 4c5dd46faf7..903d73287b4 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 5230b14e192..0f6603a622b 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 9a87960381a..ec29c3de039 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 ef456c825d0..0e6c9b7c65d 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 3ee7643c936..e881230014b 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 31baab7883d..cb39329ebab 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 5f448a2e529..443f8b95c93 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 d02ad4396d0..975126772d5 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 cac360f47a8..7f16643e4f8 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 cce92f7794a..197838ae7dc 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 f6e364b044c..3c3da7695e1 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 215801cc7c5..dcedef41f8c 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 558729d4040..cf51a439e06 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 a859f6fd3df..be08ac36a47 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 215801cc7c5..23fbdf2cc0d 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 0614fd18b68..ae916d70667 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 cc5672a63b8..97abf5d3d0c 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 ab50cc0e5e8..f0f927fcc71 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 4ad673ea7db..31d089f53ec 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 3d80794d682..373d36bb397 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 7137da9d868..431644cce7b 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 6d14401f970..8180ef61c03 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 cb9c7a2788b..74e1d5075ae 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 00000000000..a1391215900 --- /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 6310540cb39..1801a2b0861 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 3f3d2556456..8173f4d7fa0 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 45e87d4625b..43257c239a9 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 6baa206871d..c4e46912a51 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 738b64c6409..c8483acd34b 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 5e1483813a8..6eb00ad2723 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 421a29064f7..dbe14be1526 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 5260b0f7170..877622243c8 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 00000000000..b82ab8fa03b --- /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 788a8c5a11d..724dafff28f 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 a412a0914c6..632d9aecd76 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 0c40b77035d..ebfc1687301 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 f7927ce0f96..dc13644ad0d 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 69796cec02d..f3d7c392109 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 907a98fe73a..7ad0c5b0181 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 3277ea6fdad..01526400165 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 b53e31046b6..99bdc934929 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 118803f915b..5b1861a0e40 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 2b4ce98a901..40bdfa3c7d3 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 595e17d1698..91bf7e07868 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 4b712187d4c..6c55ac02969 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 07740adf2e1..4a3011c880b 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 7f6732b282e..e9dc0dfa39b 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 19d983fd718..cc484bd5660 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 e6180bc7ead..0ef5f6df6a4 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 fbdb51209a0..f26c9cba54c 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 2ac7bbe8ac4..c7b88a9b3d8 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 00000000000..364238e98a7 --- /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 f25c2c4dcbc..4a47ab1befa 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 932be8297f1..86b71fc8c92 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 82ab5db4452..ec29a112b61 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 baf8fbcbba4..4a3045201e2 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 c260b24ceeb..b662dbea64c 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 e2ed58741bd..c3e7ade05d9 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 e9b7350a2fe..470975e2117 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 eeceb373ce8..341001a0898 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 118803f915b..415cc4256f3 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 472765dfba5..2ca8a3c7cfa 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 071014416e3..1f8a5b838f3 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 e17dece7277..6f186c16e3b 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 59687ef8ad6..75be33560ca 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 0b9c8c36f08..e0a3118ee05 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 df3f20ee792..b75982d16fa 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 0dd1218ec1a..518070a76ef 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 c369993fa0d..252d1a5ebd2 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 00000000000..a1391215900 --- /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 8a25e8b76a0..757cb3baee9 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 f5d8a4d53a0..ae71f96d361 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 d49ed2e70d5..07106d89d63 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 f8853cd2d47..5a1d7707f55 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 929bcbbc248..49112fc0c20 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 118803f915b..cdc7ae85736 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 8024f3e962d..c33fa936300 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 ee01e31d21c..0bfb57d149f 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 3f9ee36865e..ad26536d10b 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 9c4482a2e00..1b348109b8d 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 e5fc7179f86..59519c251ef 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 63db1d8237c..f0c1ec35450 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 f238d2952cb..e9321950874 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 263ce28f293..bc2821c0881 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 80e09569cf5..c95e8ad4747 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 d6b6c51e6f5..ddd40e10d82 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 6759159b1db..9c0d532f298 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 7e37a1c1f9d..7d30d65b918 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 330cd15fb59..3dfad5a493d 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 cf201a68523..085e6fe1643 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 61ccae30de9..9170cdf481f 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 2a94c2fa87d..4faca6d756e 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 7b04113d7a2..18743aada0a 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 d6315fef3d1..26e755cad5b 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 2dbff2ebe2c..5660d4fcf6c 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 00000000000..111756af3e3 --- /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 3abe8e270af..6d3961cc258 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 44941e91084..b8cb77ffa99 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 7750317d34d..d1e9ebe7f19 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 57a99233d13..d1a0acdec26 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 7fc549d5a9a..5f704f70850 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 4271012e41c..2914382e820 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 5f47bf22e5e..d65df6be671 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 ffffd249c09..233b6a995b3 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 0ed8311339f..6317fdf61d5 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 1f731e6efbd..8d54f81a6e4 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 e0ad2d03cff..25805020ed8 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 d706ff53238..17bf83158b7 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 65638dad15e..95837352502 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 e1e7e8ff079..36896d9fa24 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 1829e51815d..bf3e406a861 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 a73e4283fb1..5e57ddd8b8b 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 cdd1dc4b215..1628a721733 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 fb93783d124..1fcf961a363 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 3ad2637a5d2..141f8f31ddd 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 87e5fd2cd8d..47d2a624cd3 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 7effe92ee63..b95ec27a3bf 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 b5b12c25c03..aaa8db2b70d 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 181389950b9..9faf567662a 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 1ddcf479295..202bc8e0722 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 af900a992a6..ee39e67ae73 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 7723b5bbdb9..9e6d0193fae 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 237fb7e22a4..1422144b0e6 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 1e6a773c02d..971d6476a23 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 414d71d2d52..14a404a8c66 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 9a00d825777..c2d086e8be1 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 cf8474a3762..60dc47229e2 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 bab52704f79..cdc7ae85736 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 00000000000..ef89a2c81d9 --- /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 5b17c464742..35b39838f5d 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 99defca3e95..d137f362508 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 049ed9ce79e..92dac2e9b3d 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 3f2c11281fa..c86a90e1f09 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 9c703a704e8..1455ed91a53 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 58a68ae1aa4..2ba577de4c5 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 9d11ed63273..b8b9d35a238 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 10e5ee24ffd..6708cec1358 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 27de1a7fb0d..a55c760ec81 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 9992a30caea..ef134dc9064 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 105be4965c4..b6aa0fe5365 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 32c00287a34..ba974da6225 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 210eebdf357..dc040fc03d0 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 bd5e517481f..8813a4d71bf 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 9039fcf27bd..3f447b48d86 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 b52262e8602..a891a74d3ad 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 5561ddd5d2a..5f3d7c8b435 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 fd4186ecf03..1af8ebfcb4e 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 f6af3b500f2..bb70337158e 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 59bd52f3b0e..d3649e44a8d 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 00000000000..adba199dc48 --- /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 e2d1540a3c1..2e345a75b44 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 cd3d44f62e4..d76c733603d 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 e02eefac9db..5bc6b4a7b0e 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 088aa6f754b..a63ada73fa9 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 00000000000..e9c083d9afc --- /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 00000000000..14e705836b0 --- /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 00000000000..6e02b8df2c0 --- /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 a884d21ce4a..2c025c4e6d4 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 4eac27f1b1a..af128ba3bb3 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 f8c2e32c5df..3e3a14f0407 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 00000000000..50b6e62d751 --- /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 00000000000..4c847a0fb68 --- /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 00000000000..5c5d742654c --- /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 00000000000..761aa3467a5 --- /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 00000000000..d8f46a3130b --- /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 00000000000..b25a8cbef92 --- /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 00000000000..f97f10c1efb --- /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 eab8417f7fa..fcf9a27cd1b 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 e7aea4fbd89..a244cc58ab3 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 2f816f71c3c..fa4af800248 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 e7e994b2a56..1d8c0e543fe 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 cbc8f9fb354..b04b6172969 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 54e03b32b8a..d8f4fa09850 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 ed809f65e7c..5d50d06388d 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 dd977e96860..18799057793 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 ce96a2c2ab6..2bc22d498e1 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 02e2d48ac9c..91087c73a58 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 1c490c0ea47..e59ce426deb 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 ab245da07ee..16fca72db26 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 bab52704f79..877622243c8 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 5ede2b35303..aa2ec61978b 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 aff80de710d..f852feefb0e 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 e8d227cdedc..e735bf09f7d 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 3544bd22dc3..5944daf63ca 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 f1d52c417a8..f19dd73dde9 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 34312fd03a1..455c89deba4 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 23d8a479b73..25b4e7bd72b 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 66aab37914d..1e13fb79fd2 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 4e39e2bdf6f..fd64b2140e2 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 3f7c4990cd0..83187c38090 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 912cdfe64aa..c4a52891406 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 ec20d3a7b26..c004141cac4 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 e4ad51b4d02..fe1c8f0f259 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 be8651c1043..fe565e503a1 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 f6a55f9897c..d35c59bcc40 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 6a74424c888..45b5ef57ba5 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 00000000000..3271c6bce45 --- /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 929c1cdde06..b90ad093a8d 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 67a4de8faf8..06f79799986 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 bab52704f79..5b1861a0e40 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 ef7089357ab..43de6c1ca89 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 ff993f6176e..ea4c841cd13 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 667512e722b..4f6e62a880c 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 65fe6161ed0..c852bd3a08e 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 73452c7fea7..372cdfed20f 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 27721f76d80..7b460c05717 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 53300521d3f..320ea67bf98 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 3b724d830f2..1ed862e8f84 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 9f2e620391a..e393d8ffb65 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 aecb2ee553f..8d1f457ae10 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 4b3af37a706..59ffb563687 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 1124fde09c4..b8f1bab24bb 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 7f36d019843..6e078b24305 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 808556d2a30..d538ac09c4b 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 2ffe32a5fef..096e339732c 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 f9bbee69757..042f6c824ce 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 7c97846844d..099a7ebcd3b 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 806a63c257a..a8dcd409a81 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 bee37751f76..4583eb47823 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 749746bd52a..0d185423056 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 20cabc1bbe7..4070ff01357 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 205f1ea320d..30ff37ccc1e 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 bab52704f79..cdc7ae85736 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 b24b965d004..fe3b7448ac8 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 00000000000..a1391215900 --- /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 4d7c3f964d4..7d8c1e635df 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 f1c469f8e5f..e20c18577e5 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 abc00e150fb..1cf10eb6175 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 555cbf3406d..5576af325de 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 9d5c561fb7a..7fca0f12f44 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 1b4b0047b66..0c08e11eee8 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 f66d14e030a..cb9bc10730c 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 068d8a80913..c5948ef0f26 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 3c5f96d7002..3a835ab5d57 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 6bf5e8416b0..0ad70ba6ac7 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 1e427f465db..57bc15272c9 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 f325bdfa640..6b745e37a8e 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 d67f873296b..9ef53ffc9b7 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 9909277deed..e2e19a56b0a 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 3e05e3e9e7a..bea659e039a 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 4373918fcad..f4760538f23 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 0e2e754f90c..b76849ed295 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 5499c9ae10d..8e37ecbf503 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 899af053888..e790e3d3d97 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 134104c26d6..4a0701cdaf9 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 c370d68d126..63ba3b2696a 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 0b1641b60ec..45a09f368f7 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 0f034693986..24f5e675ceb 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 27344f27bc8..f4949d3832d 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 df951b1e617..91b755c797b 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 a73ad869c2c..8e0294b87b5 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 5087afff1bd..b017910c279 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 32ac668aa29..70c290590b1 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 27cedabdefa..7f2ca352130 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 c99e65e247c..ace40a192a3 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 6c58f72eef7..9a84ba81ced 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 138602fb472..7ade0ad11a5 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 d564f7d754f..4b15c926247 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 a6b86e99d42..b50784422b2 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 594bd343ae1..f80d57e35c8 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 e8036c15205..7d46b4ed96b 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 416c4582902..4a1a467a413 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 e055c1c1eb9..0fd6807133d 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 a5facf43b79..4c4385ec88b 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 b7bc1745d7d..7af0b69e97e 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 a67e2504cf6..128d16d9c4c 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 b40afa3ffcd..3b1aac109e0 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 8a6bf7c8ddd..4a2d8b6f54f 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 8680df5adc5..dd95266866f 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 85b0863bd7e..dd637072413 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 87024302a45..e9a64002372 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 d03b04c6261..9b5566b1ea4 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 e31b6d6443c..b07fd2a3386 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 bab52704f79..9ffc0f96a83 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 b116200db57..bcf941fe39b 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 23e59bbc57f..100dc55ecae 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 3f5afd4d729..e83a8386341 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 d983f0aa48d..dc284985ee2 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 2665de72b15..8b0f37864c5 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 97ffaecd0f2..79fc5c3db1f 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 9bfe5cc02a0..d6c3dbbc837 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 3c3e1875f57..ee65d6e5c8c 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 fdad6c16b61..cd3c573a4dd 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 24c5868d0e9..e05ea2942f2 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 00000000000..93e620b6b5e --- /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 74252f1b493..83923b660c2 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 d8802b2bfb8..abd099a404b 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 463745b6bbf..fb53e59f71f 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 d11e8bdc709..b164109231e 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 cfd4d626b5a..b125dae0bc6 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 91ade7d132b..beb582b14ea 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 00000000000..a1391215900 --- /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 4266ec99ffb..e0fb76cd6cb 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 884c6429cad..504ae7eadba 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 81511935386..c370d342e1f 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 028a6302d0b..ca7149b26b5 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 4d431a31479..2a4a519369a 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 b5512c539fe..af8efe8ea86 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 2a7b292c363..7f63b834bb9 100644 --- a/homeassistant/components/open_meteo/config_flow.py +++ b/homeassistant/components/open_meteo/config_flow.py @@ -5,10 +5,11 @@ from typing import Any 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 @@ class OpenMeteoFlowHandler(ConfigFlow, domain=DOMAIN): 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 d6125729191..ee59928c0c8 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 00000000000..f0c95ffbe56 --- /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 9f1d307c14a..a1b541c177d 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 ef0210e3067..57798e31ad9 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 fe0f3a638bf..93d1a4d060e 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 e1abebd40f1..65fb704e4c7 100644 --- a/tests/components/flux_led/__init__.py +++ b/tests/components/flux_led/__init__.py @@ -110,6 +110,7 @@ def _mocked_bulb() -> AIOWifiLedBulb: 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 00000000000..c641d88c8e2 --- /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 7f668383c4b..4ca7c5a9105 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 d81d63e3af1..1426c749552 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 @@ from homeassistant.const import ( 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 6b203f51e98..daedcfef4c1 100644 --- a/tests/components/google_travel_time/test_sensor.py +++ b/tests/components/google_travel_time/test_sensor.py @@ -16,20 +16,6 @@ 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.""" 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 7bee7669e16..1a752626988 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 04675666356..d2bb647b2ca 100644 --- a/tests/components/waze_travel_time/conftest.py +++ b/tests/components/waze_travel_time/conftest.py @@ -5,11 +5,13 @@ from WazeRouteCalculator import WRCError 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 00000000000..f56e8c5892e --- /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 015850ba1b8..8a57a34b739 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.components.waze_travel_time.const import ( ) 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 00000000000..0409b037394 --- /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 6b9b77242ba..893c0e48bd0 100644 --- a/homeassistant/components/google_travel_time/const.py +++ b/homeassistant/components/google_travel_time/const.py @@ -26,8 +26,6 @@ TIME_TYPES = [ARRIVAL_TIME, DEPARTURE_TIME] 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 6960361d9a1..3ee2a18455c 100644 --- a/homeassistant/components/google_travel_time/sensor.py +++ b/homeassistant/components/google_travel_time/sensor.py @@ -35,7 +35,6 @@ from .const import ( CONF_UNITS, DEFAULT_NAME, DOMAIN, - TRACKABLE_DOMAINS, ) _LOGGER = logging.getLogger(__name__) @@ -109,17 +108,10 @@ class GoogleTravelTimeSensor(SensorEntity): 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 @@ class GoogleTravelTimeSensor(SensorEntity): 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 @@ class GoogleTravelTimeSensor(SensorEntity): 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) + self._resolved_origin = find_coordinates(self.hass, self._origin) + self._resolved_destination = find_coordinates(self.hass, self._destination) - if hasattr(self, "_destination_entity_id"): - self._destination = find_coordinates(self.hass, self._destination_entity_id) - - 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 554f3ecf6d8..37278543dfb 100644 --- a/homeassistant/components/waze_travel_time/const.py +++ b/homeassistant/components/waze_travel_time/const.py @@ -25,6 +25,3 @@ UNITS = [CONF_UNIT_SYSTEM_METRIC, CONF_UNIT_SYSTEM_IMPERIAL] 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 @@ class WazeTravelTime(SensorEntity): 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 @@ class WazeTravelTimeData: 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 d146621b416..64239a7fae1 100644 --- a/script/hassfest/manifest.py +++ b/script/hassfest/manifest.py @@ -248,12 +248,14 @@ MANIFEST_SCHEMA = vol.Schema( 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 e6975ddb47b..6a892b2791d 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 57798e31ad9..55124fe8c41 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 93d1a4d060e..bc0d7023804 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 2763bd3fe42..7319139d3c2 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -524,7 +524,10 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity): 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 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity): 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 26557ca6daf..bac88880cdb 100644 --- a/homeassistant/components/sleepiq/__init__.py +++ b/homeassistant/components/sleepiq/__init__.py @@ -18,11 +18,15 @@ import homeassistant.helpers.config_validation as cv 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 d2aeae06e8a..53611edc66b 100644 --- a/homeassistant/components/sleepiq/binary_sensor.py +++ b/homeassistant/components/sleepiq/binary_sensor.py @@ -11,7 +11,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback 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 8cdc0398c2d..cca9253d589 100644 --- a/homeassistant/components/sleepiq/button.py +++ b/homeassistant/components/sleepiq/button.py @@ -13,7 +13,7 @@ from homeassistant.core import HomeAssistant 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 ca664f99426..a2394de20b1 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 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator _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 @@ class SleepIQDataUpdateCoordinator(DataUpdateCoordinator[dict[str, dict]]): 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 141b94fa72d..6d0c8784eec 100644 --- a/homeassistant/components/sleepiq/entity.py +++ b/homeassistant/components/sleepiq/entity.py @@ -14,18 +14,23 @@ from homeassistant.helpers.update_coordinator import ( 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 @@ class SleepIQSensor(CoordinatorEntity): 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 @@ class SleepIQSensor(CoordinatorEntity): @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 dd7fdabcfb3..7d50876b1b2 100644 --- a/homeassistant/components/sleepiq/sensor.py +++ b/homeassistant/components/sleepiq/sensor.py @@ -10,7 +10,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback 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 00000000000..c8977f0ce73 --- /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 00000000000..38fc747c39d --- /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 131e02ceba0..e5a9433fad8 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 55124fe8c41..a9f467da6ff 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 bc0d7023804..6d8f5b36e83 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 cedc4fc77fe..c15d46c5158 100644 --- a/homeassistant/components/lookin/__init__.py +++ b/homeassistant/components/lookin/__init__.py @@ -153,6 +153,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) 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 58eafd3843f..6ff167d86fe 100644 --- a/homeassistant/components/lookin/entity.py +++ b/homeassistant/components/lookin/entity.py @@ -18,7 +18,7 @@ from .models import LookinData 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 @@ class LookinDeviceCoordinatorEntity(LookinDeviceMixIn, CoordinatorEntity): 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 @@ class LookinCoordinatorEntity(LookinDeviceMixIn, LookinEntityMixIn, CoordinatorE 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 3587136c4a2..f0dffe66ec0 100644 --- a/homeassistant/components/lookin/models.py +++ b/homeassistant/components/lookin/models.py @@ -13,6 +13,7 @@ from .coordinator import LookinDataUpdateCoordinator 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 5f925e2b01d..8c54896e85e 100644 --- a/homeassistant/components/homekit/type_thermostats.py +++ b/homeassistant/components/homekit/type_thermostats.py @@ -6,6 +6,8 @@ from pyhap.const import CATEGORY_THERMOSTAT 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 @@ from homeassistant.components.climate.const import ( 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 @@ from homeassistant.components.climate.const import ( 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 @@ from homeassistant.components.climate.const import ( 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 @@ from homeassistant.const import ( 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 @@ from .const import ( 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_PREFER_COOL = [ 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 @@ HC_HASS_TO_HOMEKIT_ACTION = { 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 @@ class Thermostat(HomeAccessory): # 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 @@ class Thermostat(HomeAccessory): 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 @@ class Thermostat(HomeAccessory): 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 @@ class Thermostat(HomeAccessory): @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 @@ class Thermostat(HomeAccessory): ) # 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 @@ class Thermostat(HomeAccessory): # 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 @@ class Thermostat(HomeAccessory): # 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 @@ class Thermostat(HomeAccessory): 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 a11aa9d6cb7..d1db618e7e4 100644 --- a/tests/components/homekit/test_type_thermostats.py +++ b/tests/components/homekit/test_type_thermostats.py @@ -7,12 +7,16 @@ import pytest 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 @@ from homeassistant.components.climate.const import ( 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 @@ from homeassistant.components.climate.const import ( 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 bfbe4f84d60..091bde53ae0 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -292,13 +292,12 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity): 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 2299389f12b..4f12a2b02bb 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 f1ac620f379..b4dc5755a73 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -644,7 +644,10 @@ class PlexServer: _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 a4901dcf2d2..a824bc596d0 100644 --- a/homeassistant/components/upcloud/__init__.py +++ b/homeassistant/components/upcloud/__init__.py @@ -21,16 +21,18 @@ from homeassistant.const import ( ) 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 @@ class UpCloudServerEntity(CoordinatorEntity): 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 126deba3494..ce545684da5 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 7cc27c2e1e7..d714c5183b7 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 1228bc87df1..b4278bcce36 100644 --- a/homeassistant/components/opensky/sensor.py +++ b/homeassistant/components/opensky/sensor.py @@ -26,6 +26,7 @@ from homeassistant.util import distance as util_distance, location as util_locat CONF_ALTITUDE = "altitude" +ATTR_ICAO24 = "icao24" ATTR_CALLSIGN = "callsign" ATTR_ALTITUDE = "altitude" ATTR_ON_GROUND = "on_ground" @@ -45,7 +46,7 @@ OPENSKY_ATTRIBUTION = ( ) 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 @@ class OpenSkySensor(SensorEntity): 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 @@ class OpenSkySensor(SensorEntity): 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 ef8572fedc3..fb62bbdc3dc 100644 --- a/homeassistant/components/zwave_js/button.py +++ b/homeassistant/components/zwave_js/button.py @@ -11,7 +11,7 @@ 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 .const import DATA_CLIENT, DOMAIN, LOGGER from .helpers import get_device_id, get_valueless_base_unique_id @@ -58,8 +58,24 @@ class ZWaveNodePingButton(ButtonEntity): 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 840c36b7fde..8ac909d76de 100644 --- a/homeassistant/components/zwave_js/sensor.py +++ b/homeassistant/components/zwave_js/sensor.py @@ -488,7 +488,10 @@ class ZWaveNodeStatusSensor(SensorEntity): 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 deb95e5eef4..9b5ac66b06f 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 c632f0fca17..891a417551e 100644 --- a/tests/components/zwave_js/test_sensor.py +++ b/tests/components/zwave_js/test_sensor.py @@ -15,6 +15,7 @@ from homeassistant.components.zwave_js.const import ( 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 617698b8b02..7a16225dcbb 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 a9f467da6ff..ae6697e9fcf 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 6d8f5b36e83..ae9acafcee8 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 da86b41e3b0..89c6c364860 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 d0fadc54030..368f3b97fbd 100644 --- a/tests/components/renault/const.py +++ b/tests/components/renault/const.py @@ -79,13 +79,6 @@ MOCK_VEHICLES = { 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 @@ MOCK_VEHICLES = { 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 @@ MOCK_VEHICLES = { 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 @@ MOCK_VEHICLES = { 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 758dfca4b97..35273bb3d6f 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 ae6697e9fcf..8a1ea7d86d6 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 ae9acafcee8..aa29cdb4f0c 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 2b9f1563d99..ebc3ff376f8 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 a60ac651e31..a0d6396ec30 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 075c8abbc65..283bd6442b1 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 95a5999aaf1..1e3f39a2f89 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 91bd81efef5..cf8fb02b989 100644 --- a/script/hassfest/codeowners.py +++ b/script/hassfest/codeowners.py @@ -10,7 +10,7 @@ BASE = """ # 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 e5a9433fad8..71e2e7d64b8 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 96094e6f75e..403f607e379 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 8a1ea7d86d6..2adac10bc6c 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 af47f101fbb..c4ced71605a 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 88f86034aea..9a65af96852 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 6a6264304c9..4f88b5d1369 100644 --- a/homeassistant/components/updater/__init__.py +++ b/homeassistant/components/updater/__init__.py @@ -58,16 +58,10 @@ class Updater: 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 97bc7ec4673..cd10d9168d9 100644 --- a/homeassistant/components/moon/sensor.py +++ b/homeassistant/components/moon/sensor.py @@ -4,7 +4,10 @@ from __future__ import annotations 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 @@ MOON_ICONS = { 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 8a0269ba9fe..066620b1051 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): +@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_day1"}} + 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_day1") + assert hass.states.get("sensor.moon") with patch( - "homeassistant.components.moon.sensor.dt_util.utcnow", return_value=DAY1 + "homeassistant.components.moon.sensor.moon.phase", return_value=moon_value ): - await async_update_entity(hass, "sensor.moon_day1") + await async_update_entity(hass, "sensor.moon") - assert hass.states.get("sensor.moon_day1").state == "waxing_crescent" + state = hass.states.get("sensor.moon") + assert state.state == native_value + assert state.attributes["icon"] == icon -async def test_moon_day2(hass): - """Test the Moon sensor.""" - config = {"sensor": {"platform": "moon", "name": "moon_day2"}} - - 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") - - with patch( - "homeassistant.components.moon.sensor.dt_util.utcnow", return_value=DAY2 - ): - await async_update_entity(hass, "sensor.moon_day2") - - assert hass.states.get("sensor.moon_day2").state == "waning_gibbous" - - -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 10927fc3a06..f829fd9ed39 100644 --- a/homeassistant/components/sensibo/climate.py +++ b/homeassistant/components/sensibo/climate.py @@ -35,6 +35,7 @@ from homeassistant.const import ( 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 @@ class SensiboClimate(CoordinatorEntity, ClimateEntity): 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 bf2c1aca17f..ef0475640b5 100644 --- a/homeassistant/components/sensibo/coordinator.py +++ b/homeassistant/components/sensibo/coordinator.py @@ -47,6 +47,7 @@ class SensiboDataUpdateCoordinator(DataUpdateCoordinator): 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 @@ class SensiboDataUpdateCoordinator(DataUpdateCoordinator): 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 eff953592ac..9e531249bf7 100644 --- a/homeassistant/components/sensibo/number.py +++ b/homeassistant/components/sensibo/number.py @@ -9,6 +9,7 @@ from homeassistant.components.number import NumberEntity, NumberEntityDescriptio 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 @@ class SensiboNumber(CoordinatorEntity, NumberEntity): 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 4b1d89e59b3..168eafe7047 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 2adac10bc6c..935694fe56c 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 aa29cdb4f0c..3b54aff60a1 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 c4ced71605a..fa217e89866 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 1f7e411003a..59c5ebf24a9 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 62e103f9c1d..3471099b588 100644 --- a/homeassistant/components/waze_travel_time/sensor.py +++ b/homeassistant/components/waze_travel_time/sensor.py @@ -14,6 +14,7 @@ from homeassistant.const import ( 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.device_registry import DeviceEntryType 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 @@ class WazeTravelTimeData: 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 0409b037394..2b28190e430 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 @@ from tests.common import MockConfigEntry @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 f52f751fc01..de6399c2e9f 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 935694fe56c..52751230625 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 3b54aff60a1..b68b3b2b3b9 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 | 263 +++++++++--------- 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, 464 insertions(+), 345 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 b574e68ae2f..9934cc8f481 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 @@ from homeassistant.const import ( CONF_HOST, CONF_PORT, CONF_SSL, + CONF_URL, CONF_VERIFY_SSL, Platform, ) @@ -22,7 +26,9 @@ from .const import ( 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 @@ from .const import ( 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 f226d1883a5..c9ef2d3ecc8 100644 --- a/homeassistant/components/sonarr/config_flow.py +++ b/homeassistant/components/sonarr/config_flow.py @@ -4,28 +4,21 @@ from __future__ import annotations 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 @@ class SonarrConfigFlow(ConfigFlow, domain=DOMAIN): 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 @@ class SonarrConfigFlow(ConfigFlow, domain=DOMAIN): 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 @@ class SonarrConfigFlow(ConfigFlow, domain=DOMAIN): 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 @@ class SonarrConfigFlow(ConfigFlow, domain=DOMAIN): 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 be0fa00d597..58f5c465716 100644 --- a/homeassistant/components/sonarr/const.py +++ b/homeassistant/components/sonarr/const.py @@ -7,17 +7,14 @@ CONF_DAYS = "days" 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 1d0cb2ce6f3..41f6786503d 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 @@ class SonarrEntity(Entity): 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 @@ class SonarrEntity(Entity): 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 4b1555fa3de..9c43bfed282 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 01046ded7c2..c182bb2bbeb 100644 --- a/homeassistant/components/sonarr/sensor.py +++ b/homeassistant/components/sonarr/sensor.py @@ -7,7 +7,9 @@ from functools import wraps 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.entity_platform import AddEntitiesCallback 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 @@ def sonarr_exception_handler( 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 @@ class SonarrSensor(SonarrEntity, SensorEntity): super().__init__( sonarr=sonarr, + host_config=host_config, + system_status=system_status, entry_id=entry_id, device_id=entry_id, ) @@ -155,23 +179,30 @@ class SonarrSensor(SonarrEntity, SensorEntity): 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 @@ class SonarrSensor(SonarrEntity, SensorEntity): 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 @@ class SonarrSensor(SonarrEntity, SensorEntity): ] = 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 @@ class SonarrSensor(SonarrEntity, SensorEntity): """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 @@ class SonarrSensor(SonarrEntity, SensorEntity): 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 @@ class SonarrSensor(SonarrEntity, SensorEntity): 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 2281b6cec57..b8537e11442 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 52751230625..5f12ee60725 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 b68b3b2b3b9..1832922baef 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 cd3fb8f795a..ca9fc91bd5e 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 a03ae7532d4..da8ff75df0f 100644 --- a/tests/components/sonarr/conftest.py +++ b/tests/components/sonarr/conftest.py @@ -3,15 +3,16 @@ 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 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 homeassistant.core import HomeAssistant 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_queue(): +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() -> 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_wanted(): +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() -> 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 e9ce88b233e..00000000000 --- 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 97acc2f9f82..943bed3308c 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 1a8eb0924c3..493353e2d88 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 - }, - { - "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 + "qualityProfileId":5, + "id":17 }, - "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" + "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 }, - "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 - } -] + "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 + } + ] +} diff --git a/tests/components/sonarr/fixtures/series.json b/tests/components/sonarr/fixtures/series.json index ea727c14a97..154ab7eb75e 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 00000000000..fe6198a0444 --- /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 5db7c52f469..df6212487fb 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 52e7b9b61ca..59783995d23 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 @@ from homeassistant.components.sonarr.const import ( 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 3a59f5d7cca..f4a317e3de0 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 cc15376efb1..c499dc0112f 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 a87b3b5edd6..4e741aa68e6 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 . import HomeAssistantOverkizData 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 12354f41241..4ad1c401d73 100644 --- a/homeassistant/components/overkiz/cover_entities/vertical_cover.py +++ b/homeassistant/components/overkiz/cover_entities/vertical_cover.py @@ -13,6 +13,7 @@ from homeassistant.components.cover import ( SUPPORT_STOP, CoverDeviceClass, ) +from homeassistant.components.overkiz.coordinator import OverkizDataUpdateCoordinator from .generic_cover import COMMANDS_STOP, OverkizGenericCover @@ -99,3 +100,37 @@ class VerticalCover(OverkizGenericCover): """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 45be07585d7..508ed2f876f 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 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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 83df8952278..74daf1d34e0 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 @@ class SamsungTVBridge(ABC): 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 @@ class SamsungTVBridge(ABC): # 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 @@ class SamsungTVBridge(ABC): 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 @@ class SamsungTVBridge(ABC): 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 @@ class SamsungTVLegacyBridge(SamsungTVBridge): 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 @@ class SamsungTVLegacyBridge(SamsungTVBridge): 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 @@ class SamsungTVLegacyBridge(SamsungTVBridge): 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 @@ class SamsungTVWSBridge(SamsungTVBridge): 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 @@ class SamsungTVWSBridge(SamsungTVBridge): 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 @@ class SamsungTVWSBridge(SamsungTVBridge): 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 @@ class SamsungTVWSBridge(SamsungTVBridge): 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 @@ class SamsungTVWSBridge(SamsungTVBridge): 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 fe9e4384884..8b7e0f83a49 100644 --- a/homeassistant/components/samsungtv/config_flow.py +++ b/homeassistant/components/samsungtv/config_flow.py @@ -124,11 +124,11 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """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 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """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 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 324a3d1b32f..007ab283cfd 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 db99726f20c..cb857a96afb 100644 --- a/homeassistant/components/samsungtv/media_player.py +++ b/homeassistant/components/samsungtv/media_player.py @@ -153,30 +153,32 @@ class SamsungTVDevice(MediaPlayerEntity): ) ) - 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 @@ class SamsungTVDevice(MediaPlayerEntity): 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 @@ class SamsungTVDevice(MediaPlayerEntity): 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 @@ class SamsungTVDevice(MediaPlayerEntity): 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 2aea4b22595..eb8ca745819 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 12419b21fe8..6b6aa429243 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 cbf83a90963..98031926cfd 100644 --- a/homeassistant/components/overkiz/coordinator.py +++ b/homeassistant/components/overkiz/coordinator.py @@ -6,7 +6,7 @@ import logging 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 @@ class OverkizDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Device]]): 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 c362969abf2..cd39afd9696 100644 --- a/homeassistant/components/overkiz/executor.py +++ b/homeassistant/components/overkiz/executor.py @@ -8,7 +8,6 @@ from pyoverkiz.enums.command import OverkizCommand 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 @@ class OverkizExecutor: 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 085b3ad733d..1e9f34608eb 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 5f12ee60725..b6966ffc6a1 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 1832922baef..ef3ce287d0b 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 ef0e99001c9..21e23c74eb1 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 60a8e50c523..8633534e976 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -79,6 +79,7 @@ DHCP: list[dict[str, str | bool]] = [ {'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 5cbc8a1e678..75613b3d118 100644 --- a/homeassistant/components/device_automation/__init__.py +++ b/homeassistant/components/device_automation/__init__.py @@ -7,14 +7,14 @@ from enum import Enum 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 homeassistant.requirements import async_get_integration_with_requirements 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 @@ TYPES = { } -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 00000000000..5261757c645 --- /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 00000000000..1c226ee8c29 --- /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 f2962d6544e..933c5c4c60a 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 @@ from .exceptions import InvalidDeviceAutomationConfig 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 06853dd9450..3355424b710 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -13,10 +13,7 @@ import sys 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 @@ from homeassistant.const import ( 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 5a80691fa46..1eabc33b89d 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -15,7 +15,8 @@ import async_timeout 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 @@ class _ScriptRun: 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 b365c114b03..ffbc2130f3b 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 5bb4833a796..11ba9810b9d 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 0b7e355ef24..650013dda7f 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 handle_subscribe_events( event_type, forward_events ) - connection.send_message(messages.result_message(msg["id"])) + connection.send_result(msg["id"]) @callback @@ -139,7 +140,7 @@ def handle_subscribe_bootstrap_integrations( 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 30ce647132e..7f33ec1f1ec 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 8304b093a14..130870f73f0 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 986171cbee7..40feae117a4 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -23,7 +23,7 @@ from .const import ( 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 d1b8879bf7c..a7c664091c1 100644 --- a/homeassistant/components/analytics/analytics.py +++ b/homeassistant/components/analytics/analytics.py @@ -104,7 +104,7 @@ class Analytics: @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 434c95b03b2..1ade17e452e 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -41,6 +41,8 @@ from homeassistant.helpers.device_registry import ( 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 a41329a1548..46b08ee69c3 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -23,8 +23,7 @@ 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 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 b277bd97edf..d7dde43b4e0 100644 --- a/homeassistant/components/onboarding/views.py +++ b/homeassistant/components/onboarding/views.py @@ -195,7 +195,7 @@ class CoreConfigOnboardingView(_BaseOnboardingView): 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 5e745a123f4..9a3f0dcb8b8 100644 --- a/homeassistant/components/ozw/config_flow.py +++ b/homeassistant/components/ozw/config_flow.py @@ -45,7 +45,7 @@ class DomainConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): # 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 4f88b5d1369..e1dc2fca4e4 100644 --- a/homeassistant/components/updater/__init__.py +++ b/homeassistant/components/updater/__init__.py @@ -74,7 +74,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: _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 32f406d7476..e58e9a80ccf 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -332,14 +332,14 @@ class ConfigFlow(BaseZwaveJSFlow, config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class OptionsFlowHandler(BaseZwaveJSFlow, config_entries.OptionsFlow): 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 00000000000..7e7cfadeadc --- /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 89a8c6f5c51..c5ec209500d 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 e4263eb5529..6008653e7a6 100644 --- a/tests/components/hassio/test_binary_sensor.py +++ b/tests/components/hassio/test_binary_sensor.py @@ -11,7 +11,11 @@ from homeassistant.setup import async_setup_component 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 e006cf9d829..d901432b6e3 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -16,7 +16,11 @@ from homeassistant.util import dt as dt_util 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 481ba1b578f..4969cac1d67 100644 --- a/tests/components/hassio/test_sensor.py +++ b/tests/components/hassio/test_sensor.py @@ -11,7 +11,11 @@ from homeassistant.setup import async_setup_component 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 fbd545e0506..54e2f0495cd 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 79d0a6c4791..97b705f5b6d 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 976e2b84c68..2b4db4f68a2 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 2b0f494f5f5..e2cc0ee41b0 100644 --- a/tests/components/updater/test_init.py +++ b/tests/components/updater/test_init.py @@ -7,8 +7,6 @@ from homeassistant.components import updater 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 00000000000..cfefe7a9ec4 --- /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 87d93c1a1ac..b34039cc2c9 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 952b0e67e25..8240691b247 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 f596a64e5b1..9a3db7f8294 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 1b065833adc..44ff7d46453 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 b6966ffc6a1..5fea13dd6e6 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 ef3ce287d0b..676464c76cb 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 3b01ce618b8..730ffb7fc0d 100644 --- a/homeassistant/components/fritz/switch.py +++ b/homeassistant/components/fritz/switch.py @@ -479,6 +479,17 @@ class FritzBoxProfileSwitch(FritzDeviceBase, SwitchEntity): 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 @@ class FritzBoxProfileSwitch(FritzDeviceBase, SwitchEntity): 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 00000000000..17f00a50bbe --- /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 00000000000..8ccc91a5d19 --- /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 4ad1c401d73..ec502a403ad 100644 --- a/homeassistant/components/overkiz/cover_entities/vertical_cover.py +++ b/homeassistant/components/overkiz/cover_entities/vertical_cover.py @@ -3,7 +3,13 @@ from __future__ import annotations 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 @@ class LowSpeedCover(VerticalCover): 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 833441442b2..1c5d6b1b685 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 5fea13dd6e6..925df830952 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 676464c76cb..2a44bfa6361 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 7874fb87b00..f01966d7114 100644 --- a/homeassistant/components/fritz/sensor.py +++ b/homeassistant/components/fritz/sensor.py @@ -170,7 +170,7 @@ SENSOR_TYPES: tuple[FritzSensorEntityDescription, ...] = ( 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 d3f2bd7fb7f..1d6257c48c6 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 241784aeea0..92b4e52952d 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 d95b02d3c8b..3690c1def1e 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 79c2222a6da..541c5073961 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 a014b252144..62cf2d7cc1b 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 55e2dee35cd..d318968da56 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 45c73269a72..cc9573e2b14 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 4c14fb93fe7..a0adaaab083 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 cfa7ba1e755..f5936aced5b 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 00000000000..f5cdd4cc441 --- /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 00000000000..ebe1946707e --- /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 00000000000..94d14f75360 --- /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 00000000000..744d4ad54a7 --- /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 00000000000..8c645d42aa8 --- /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 00000000000..c7f8f5c1587 --- /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 00000000000..3b36e3c8b3a --- /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 00000000000..bd7407f80b6 --- /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 00000000000..52a8ad0ee14 --- /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 00000000000..2f06619c006 --- /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 4dee8082dbf..fd0b2adba42 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 5f704f70850..59b00e329af 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 233b6a995b3..6921604bdee 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 00000000000..6aafb35d5f9 --- /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 00000000000..094e66dd3f5 --- /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 00000000000..fbc52f3b1de --- /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 00000000000..24b32e6e30a --- /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 00000000000..8646b43508e --- /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 00000000000..903848b73ea --- /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 00000000000..a826b331193 --- /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 d0290abdf98..5c7002aaa22 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 e735bf09f7d..b7005731172 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 8438be5c677..1d0b64b5054 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 9defa2971bb..28dc6dbb00c 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 60fe4c88e20..437ce96d9f1 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 11f92bfccb4..004580b5192 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 8bc58118a23..86dcb69f4c6 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 c113c06a021..9b14769b839 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 5ca9a9f847d..720db0d1bd8 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 10930df8525..d3cb5720875 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 9779a985034..eb521c1c237 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 b8f1bab24bb..a348d76f798 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 74232e9f8b7..f676005dcfe 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 f6b8b63781a..4c474ef2349 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 e05ea2942f2..4b7813b0c74 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 00000000000..7ae0b5c40a9 --- /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 0e0ca8686ef..ace4066ae3b 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 9dbedeacf5b..821bf07cf08 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.components.lutron_caseta.const import ( ) from homeassistant.const import CONF_HOST +from . import MockBridge + from tests.common import MockConfigEntry ATTR_HOSTNAME = "hostname" @@ -39,32 +41,6 @@ MOCK_ASYNC_PAIR_SUCCESS = { } -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 00000000000..89fcb65df9d --- /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 44bca818333..df3a4b8cd11 100644 --- a/homeassistant/components/plex/__init__.py +++ b/homeassistant/components/plex/__init__.py @@ -25,10 +25,12 @@ from homeassistant.helpers.dispatcher import ( 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 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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 00000000000..23e8ec103e9 --- /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 ce28357a4b1..dea976f46dd 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 @@ DEFAULT_VERIFY_SSL = True 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 11d40dbab75..0433ba836cd 100644 --- a/homeassistant/components/plex/services.py +++ b/homeassistant/components/plex/services.py @@ -31,7 +31,10 @@ async def async_setup_services(hass): 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 00000000000..b540ba2d031 --- /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 fced4bae58a..8e09f702386 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 1a752626988..1784bdad2f6 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 1dc60f4a59e..139a8448b08 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())) - def _side_effect_action_names(self): - return list(self.FRITZBOX_DATA) + list(self.FRITZBOX_DATA_INDEXED) + return self._services[service][action][index] + return self._services[service][action] + + +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 3212794fc85..79d92a1e22d 100644 --- a/tests/components/fritz/const.py +++ b/tests/components/fritz/const.py @@ -26,9 +26,758 @@ MOCK_CONFIG = { } } 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_DEVICE_INFO = { 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 00000000000..a6ff579958a --- /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 6505ee2bcaa..502757c2c67 100644 --- a/tests/components/fritz/test_config_flow.py +++ b/tests/components/fritz/test_config_flow.py @@ -26,7 +26,7 @@ from homeassistant.data_entry_flow import ( 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 00000000000..892210d0844 --- /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 00000000000..fd67321d235 --- /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 | 76 +++++++++++++------ 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, 67 insertions(+), 38 deletions(-) diff --git a/homeassistant/components/fritz/common.py b/homeassistant/components/fritz/common.py index 5706ca19486..b2a429bfa3c 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -39,6 +39,8 @@ from homeassistant.helpers.entity import DeviceInfo 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 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator): """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 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator): 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 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator): 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 180a6239d9f..0844d725522 100644 --- a/homeassistant/components/fritz/config_flow.py +++ b/homeassistant/components/fritz/config_flow.py @@ -21,6 +21,8 @@ from homeassistant.data_entry_flow import FlowResult from .common import AvmWrapper from .const import ( + CONF_OLD_DISCOVERY, + DEFAULT_CONF_OLD_DISCOVERY, DEFAULT_HOST, DEFAULT_PORT, DOMAIN, @@ -107,6 +109,7 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN): }, options={ CONF_CONSIDER_HOME: DEFAULT_CONSIDER_HOME.total_seconds(), + CONF_OLD_DISCOVERY: DEFAULT_CONF_OLD_DISCOVERY, }, ) @@ -296,6 +299,12 @@ class FritzBoxToolsOptionsFlowHandler(OptionsFlow): 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 635460db15f..f33cf463996 100644 --- a/homeassistant/components/fritz/const.py +++ b/homeassistant/components/fritz/const.py @@ -32,6 +32,9 @@ PLATFORMS = [ 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 f1cdb719741..450566f101b 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 0fa47bd8328..0a58ee686f3 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 | 21 +++++++----- .../components/media_source/local_source.py | 33 +++++++++---------- tests/components/media_source/test_init.py | 13 ++++++-- 3 files changed, 38 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/media_source/__init__.py b/homeassistant/components/media_source/__init__.py index 3be5bf040d7..d990b0b1f3d 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"]), - ) + return - connection.send_result(msg["id"], {"url": url, "mime_type": media.mime_type}) + 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 76598eac963..66baa0eaa8c 100644 --- a/homeassistant/components/media_source/local_source.py +++ b/homeassistant/components/media_source/local_source.py @@ -56,10 +56,6 @@ class LocalSource(MediaSource): 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 @@ class LocalSource(MediaSource): 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 7aae72475cb..a32535a5657 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 2c4ed37f955..51b88bb4f79 100644 --- a/homeassistant/components/litterrobot/entity.py +++ b/homeassistant/components/litterrobot/entity.py @@ -9,7 +9,7 @@ from typing import Any 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 @@ class LitterRobotControlEntity(LitterRobotEntity): 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 @@ class LitterRobotConfigEntity(LitterRobotControlEntity): 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 3aa86dcc93a..49fba822a6f 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 @@ UPDATE_INTERVAL_SECONDS = 20 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 @@ class LitterRobotHub: 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 @@ class LitterRobotHub: 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 95e2090f061..c72dfbee1bd 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 99104702f26..53468caa29d 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -97,14 +97,6 @@ IGNORED_MODULES: Final[list[str]] = [ "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 cef9db620b8..ffc4d39a89b 100644 --- a/homeassistant/components/firmata/__init__.py +++ b/homeassistant/components/firmata/__init__.py @@ -190,7 +190,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b 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 1f0052732ea..002775edb0c 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 @@ FirmataPinType = Union[int, str] 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 @@ board %s: %s", 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 @@ board %s: %s", 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 091d724229c..da722b51897 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_OUTPUT = "OUTPUT" 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 0f248e0b9d7..0e66656421b 100644 --- a/homeassistant/components/firmata/entity.py +++ b/homeassistant/components/firmata/entity.py @@ -20,7 +20,7 @@ class FirmataEntity: 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 d42e72f992a..de511058114 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 6dadb07fd63..190889914b3 100644 --- a/homeassistant/components/firmata/pin.py +++ b/homeassistant/components/firmata/pin.py @@ -3,6 +3,7 @@ from __future__ import annotations 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 @@ class FirmataBoardPin: 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 @@ class FirmataBoardPin: class FirmataBinaryDigitalOutput(FirmataBoardPin): """Representation of a Firmata Digital Output Pin.""" + _state: bool + def __init__( self, board: FirmataBoard, @@ -92,6 +96,8 @@ class FirmataBinaryDigitalOutput(FirmataBoardPin): class FirmataPWMOutput(FirmataBoardPin): """Representation of a Firmata PWM/analog Output Pin.""" + _state: int + def __init__( self, board: FirmataBoard, @@ -139,12 +145,14 @@ class FirmataPWMOutput(FirmataBoardPin): 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 @@ class FirmataBinaryDigitalInput(FirmataBoardPin): 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 c72dfbee1bd..887a65394b7 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 53468caa29d..a78e07e057b 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -48,14 +48,6 @@ IGNORED_MODULES: Final[list[str]] = [ "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 c11b9874bae..1971b14b2be 100644 --- a/homeassistant/components/tradfri/__init__.py +++ b/homeassistant/components/tradfri/__init__.py @@ -5,7 +5,7 @@ from datetime import datetime, timedelta 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 async_setup_entry( ) 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 8c4f483b9a8..a2bd28e3868 100644 --- a/homeassistant/components/tradfri/base_class.py +++ b/homeassistant/components/tradfri/base_class.py @@ -9,7 +9,7 @@ from typing import Any, cast 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 @@ def handle_error( """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 197cf26b41f..c7652fe97b9 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -183,7 +183,14 @@ MQTT_WILL_BIRTH_SCHEMA = vol.Schema( 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 829c96671a9..6fe4683c1fc 100644 --- a/homeassistant/components/media_player/browse_media.py +++ b/homeassistant/components/media_player/browse_media.py @@ -56,6 +56,7 @@ class BrowseMedia: 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 @@ class BrowseMedia: 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 @@ class BrowseMedia: "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 d990b0b1f3d..e2bd1b4903b 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 4f12a2b02bb..663941de77a 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 c68a311d2b6..896968557c1 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 c1fcd510c23..a725129bed6 100644 --- a/tests/components/media_player/test_init.py +++ b/tests/components/media_player/test_init.py @@ -5,6 +5,7 @@ from http import HTTPStatus 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 a32535a5657..e36ccdac931 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 f2c8db3879b..ddc50fb7702 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 38a0ff15af3..83e358acc34 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 @@ class GeniusEntity(Entity): """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 @@ class GeniusDevice(GeniusEntity): 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 @@ class GeniusZone(GeniusEntity): 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 1e54df52820..f00019361e5 100644 --- a/homeassistant/components/geniushub/binary_sensor.py +++ b/homeassistant/components/geniushub/binary_sensor.py @@ -42,9 +42,9 @@ class GeniusBinarySensor(GeniusDevice, BinarySensorEntity): 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 fad66c49312..20acb533a2c 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 @@ class GeniusBattery(GeniusDevice, SensorEntity): 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 @@ class GeniusBattery(GeniusDevice, SensorEntity): 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 @@ class GeniusIssue(GeniusEntity, SensorEntity): 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 f1ff8444d28..a64aa1345e1 100644 --- a/homeassistant/components/geniushub/water_heater.py +++ b/homeassistant/components/geniushub/water_heater.py @@ -73,7 +73,7 @@ class GeniusWaterHeater(GeniusHeatingZone, WaterHeaterEntity): @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 887a65394b7..95c83dd1a5f 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 a78e07e057b..ae435c16ef0 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -48,11 +48,6 @@ IGNORED_MODULES: Final[list[str]] = [ "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 42b510f9417..6e2ee0fd58c 100644 --- a/homeassistant/components/zwave_me/__init__.py +++ b/homeassistant/components/zwave_me/__init__.py @@ -9,7 +9,7 @@ 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 homeassistant.helpers.entity import DeviceInfo, Entity from .const import DOMAIN, PLATFORMS, ZWaveMePlatform @@ -104,9 +104,22 @@ class ZWaveMeEntity(Entity): 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 1a7177ca470..8863cd6ebf7 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 925df830952..66c31d0b25d 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 2a44bfa6361..3ebda80093b 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 49fba822a6f..43d60e534ea 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 4cdb7ec0c42..ffd82307940 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 @@ class FireServiceRotaClient: 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 c175e58cae8..9e4d5b123f5 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 @@ from homeassistant.helpers.update_coordinator import ( 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 @@ class ResponseBinarySensor(CoordinatorEntity, BinarySensorEntity): 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 @@ class ResponseBinarySensor(CoordinatorEntity, BinarySensorEntity): 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 a17892a8591..66878b73145 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 @@ class IncidentsSensor(RestoreEntity, SensorEntity): 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 7202420e571..e625ac5deb5 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 @@ class ResponseSwitch(SwitchEntity): 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 @@ class ResponseSwitch(SwitchEntity): 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 95c83dd1a5f..dbbbe824540 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 ae435c16ef0..8842b42fbd3 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -44,10 +44,6 @@ IGNORED_MODULES: Final[list[str]] = [ "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 f92cf0c8d30..eded3bec16a 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 8057de55966..5a094c10987 100644 --- a/homeassistant/components/octoprint/sensor.py +++ b/homeassistant/components/octoprint/sensor.py @@ -13,7 +13,7 @@ from homeassistant.components.sensor import ( ) 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) - entities.append(OctoPrintStatusSensor(coordinator, device_id)) - entities.append(OctoPrintJobPercentageSensor(coordinator, device_id)) - entities.append(OctoPrintEstimatedFinishTimeSensor(coordinator, device_id)) - entities.append(OctoPrintStartTimeSensor(coordinator, device_id)) + config_entry.async_on_unload(coordinator.async_add_listener(async_add_tool_sensors)) + + 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 1784bdad2f6..0d79bb02a4a 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 6e95089ccbe..6f17379dfbf 100644 --- a/homeassistant/components/tolo/__init__.py +++ b/homeassistant/components/tolo/__init__.py @@ -28,6 +28,7 @@ PLATFORMS = [ 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 bfd700bb955..77bee92d521 100644 --- a/homeassistant/components/tolo/const.py +++ b/homeassistant/components/tolo/const.py @@ -11,3 +11,7 @@ DEFAULT_MIN_TEMP = 20 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 00000000000..85d80756020 --- /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 bcdb7db0165..714667cc1f8 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 . import ToloSaunaCoordinatorEntity, ToloSaunaUpdateCoordinator 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 | 303 ++++++++---------- .../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, 326 insertions(+), 394 deletions(-) create mode 100644 homeassistant/components/powerwall/models.py diff --git a/.strict-typing b/.strict-typing index cd148430340..be25c50ae38 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 8d91b984d46..10504e2aa06 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 tesla_powerwall import ( 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 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, - 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 @@ _LOGGER = logging.getLogger(__name__) MAX_LOGIN_FAILURES = 5 - -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) +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" -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", - ) +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 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 = PowerwallRuntimeData( + api_changed=False, + base_info=base_info, + http_session=http_session, + login_failed_count=0, + coordinator=None, + ) - 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 - - _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() + runtime_data[POWERWALL_COORDINATOR] = coordinator + + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = runtime_data + hass.config_entries.async_setup_platforms(entry, PLATFORMS) return True -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 - - -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 5c8ebbdcf19..868d9e3076d 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.config_entries import ConfigEntry 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" + _attr_name = "Powerwall Connected to Tesla" + _attr_device_class = BinarySensorDeviceClass.CONNECTIVITY @property - def device_class(self): - """Device Class.""" - return 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" + _attr_name = "Grid Services Active" + _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_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" + _attr_name = "Powerwall Charging" + _attr_device_class = BinarySensorDeviceClass.BATTERY_CHARGING @property - def device_class(self): - """Device Class.""" - return 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 9814a52d8a0..08e9f90df1b 100644 --- a/homeassistant/components/powerwall/config_flow.py +++ b/homeassistant/components/powerwall/config_flow.py @@ -9,6 +9,7 @@ from tesla_powerwall import ( MissingAttributeError, Powerwall, PowerwallUnreachableError, + SiteInfo, ) import voluptuous as vol @@ -23,11 +24,12 @@ from .const import DOMAIN _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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): }, ) - 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 b2f0e3afe80..b2738bce4ac 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 ae647a080c0..20871944663 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 55c7ab41e64..be5d4678e27 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 00000000000..472d9e59304 --- /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 da0e7e7f599..a48726211b2 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 @@ from .const import ( 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 @@ class PowerWallEnergySensor(PowerWallEntity, SensorEntity): ) @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 @@ class PowerWallEnergyDirectionSensor(PowerWallEntity, SensorEntity): ) @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 dbbbe824540..b508045d623 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 66c31d0b25d..75ea65b43c4 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 3ebda80093b..f14ef8acb71 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 44ff7d46453..89631a4fc1e 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 1e3f39a2f89..fe8962e4f1e 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -122,8 +122,8 @@ 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 """ 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 | 286 +++++++++++++++++-- 3 files changed, 310 insertions(+), 39 deletions(-) diff --git a/homeassistant/components/mqtt/config_flow.py b/homeassistant/components/mqtt/config_flow.py index 23e2a0d1e81..3f93e50829a 100644 --- a/homeassistant/components/mqtt/config_flow.py +++ b/homeassistant/components/mqtt/config_flow.py @@ -33,6 +33,8 @@ from .const import ( ) 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 try_connection(broker, port, username, password, protocol="3.1"): 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 f16a0e5e83a..d9aab02e821 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 a9a96df4f8f..68ba2a040b8 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.common import ( ) 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 @@ async def test_subscribe_deprecated(hass, mqtt_mock): 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): - """Test for setup failure if connection to broker is missing.""" +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) - assert "Failed to connect to MQTT server:" in caplog.text + 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) + + # 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 @@ async def test_setup_uses_certificate_on_certificate_set_to_auto(hass): # 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 test_custom_birth_message(hass, mqtt_client_mock, mqtt_mock): 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 test_default_birth_message(hass, mqtt_client_mock, mqtt_mock): 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 test_default_birth_message(hass, mqtt_client_mock, mqtt_mock): 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 test_delayed_birth_message(hass, mqtt_client_mock, mqtt_config): 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 68ba2a040b8..e589e447a01 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 40feae117a4..986171cbee7 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -23,7 +23,7 @@ from .const import ( 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 a7c664091c1..d1b8879bf7c 100644 --- a/homeassistant/components/analytics/analytics.py +++ b/homeassistant/components/analytics/analytics.py @@ -104,7 +104,7 @@ class Analytics: @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 1ade17e452e..434c95b03b2 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -41,8 +41,6 @@ from homeassistant.helpers.device_registry import ( 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 46b08ee69c3..a41329a1548 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -23,7 +23,8 @@ 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 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 d7dde43b4e0..b277bd97edf 100644 --- a/homeassistant/components/onboarding/views.py +++ b/homeassistant/components/onboarding/views.py @@ -195,7 +195,7 @@ class CoreConfigOnboardingView(_BaseOnboardingView): 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 9a3f0dcb8b8..5e745a123f4 100644 --- a/homeassistant/components/ozw/config_flow.py +++ b/homeassistant/components/ozw/config_flow.py @@ -45,7 +45,7 @@ class DomainConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): # 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 e1dc2fca4e4..4f88b5d1369 100644 --- a/homeassistant/components/updater/__init__.py +++ b/homeassistant/components/updater/__init__.py @@ -74,7 +74,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: _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 e58e9a80ccf..32f406d7476 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -332,14 +332,14 @@ class ConfigFlow(BaseZwaveJSFlow, config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class OptionsFlowHandler(BaseZwaveJSFlow, config_entries.OptionsFlow): 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 7e7cfadeadc..00000000000 --- 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 c5ec209500d..89a8c6f5c51 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 6008653e7a6..e4263eb5529 100644 --- a/tests/components/hassio/test_binary_sensor.py +++ b/tests/components/hassio/test_binary_sensor.py @@ -11,11 +11,7 @@ from homeassistant.setup import async_setup_component 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 d901432b6e3..e006cf9d829 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -16,11 +16,7 @@ from homeassistant.util import dt as dt_util 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 4969cac1d67..481ba1b578f 100644 --- a/tests/components/hassio/test_sensor.py +++ b/tests/components/hassio/test_sensor.py @@ -11,11 +11,7 @@ from homeassistant.setup import async_setup_component 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 54e2f0495cd..fbd545e0506 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 97b705f5b6d..79d0a6c4791 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 2b4db4f68a2..976e2b84c68 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 e2cc0ee41b0..2b0f494f5f5 100644 --- a/tests/components/updater/test_init.py +++ b/tests/components/updater/test_init.py @@ -7,6 +7,8 @@ from homeassistant.components import updater 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 cfefe7a9ec4..00000000000 --- 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 b34039cc2c9..87d93c1a1ac 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 | 41 ++++---- 8 files changed, 180 insertions(+), 150 deletions(-) diff --git a/.strict-typing b/.strict-typing index be25c50ae38..74a255a7f96 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 f069605d438..112f29db333 100644 --- a/homeassistant/components/deconz/__init__.py +++ b/homeassistant/components/deconz/__init__.py @@ -12,11 +12,14 @@ from homeassistant.const import ( 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,16 +36,27 @@ 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 + 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 - if not hass.data[DOMAIN]: + gateway = hass.data[DOMAIN][config_entry.entry_id] = DeconzGateway( + hass, config_entry, api + ) + + 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) - hass.data[DOMAIN][config_entry.entry_id] = gateway - - await gateway.async_update_device_registry() + 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 d197f27910b..3bd0278a27a 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 @@ from .const import ( 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 @@ class DeconzGateway: 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 @@ class DeconzGateway: # 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 @@ class DeconzGateway: @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 @@ class DeconzGateway: 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 @@ class DeconzGateway: 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 @@ class DeconzGateway: 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 @@ class DeconzGateway: 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 aeb528c0ac9..4b840532fa6 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 @@ from homeassistant.helpers.entity_registry import ( 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 b508045d623..adb0cb25297 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 8842b42fbd3..8d542290458 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -23,19 +23,16 @@ IGNORED_MODULES: Final[list[str]] = [ "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 8a449456fde..3a4f6b907af 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.config_flow import DECONZ_MANUFACTURERURL 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): +@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.Unauthorized, - ), pytest.raises(AuthenticationRequired): - assert await get_gateway(hass, ENTRY_CONFIG, Mock(), Mock()) is False - - -async def test_get_gateway_fails_cannot_connect(hass): - """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 e50ac41d63d..05fb708b75f 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 @@ from homeassistant.components.deconz.const import ( 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,22 +42,6 @@ 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_no_available_bridge(hass): - """Test setup entry fails if deCONZ is not available.""" - with patch( - "pydeconz.DeconzSession.refresh_state", side_effect=asyncio.TimeoutError - ): - 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) @@ -67,6 +51,29 @@ async def test_setup_entry_successful(hass, aioclient_mock): assert hass.data[DECONZ_DOMAIN][config_entry.entry_id].master +async def test_setup_entry_fails_config_entry_not_ready(hass): + """Failed authentication trigger a reauthentication flow.""" + with patch( + "homeassistant.components.deconz.get_deconz_session", + side_effect=CannotConnect, + ): + await setup_deconz_integration(hass) + + assert hass.data[DECONZ_DOMAIN] == {} + + +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): """Test setup entry is successful with multiple gateways.""" config_entry = await setup_deconz_integration(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 89631a4fc1e..472202859e2 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 b907cc50cbb..4885e717b30 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 75d7905ff0e..74ea6e296b5 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 803076e44dc..fa1410521ae 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 75ea65b43c4..bc33d5b8012 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 f14ef8acb71..69ea303f3f3 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 009b8e5c775..e542c77516e 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 bc33d5b8012..d3044a7bf88 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 69ea303f3f3..f85a5a6f779 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 30778edc493..cc453f14691 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 @@ class SonosDiscoveryFlowHandler(DiscoveryFlowHandler[Awaitable[bool]]): "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 490b67c03bd..5efb0776c41 100644 --- a/homeassistant/components/fritzbox_callmonitor/sensor.py +++ b/homeassistant/components/fritzbox_callmonitor/sensor.py @@ -55,6 +55,7 @@ _LOGGER = logging.getLogger(__name__) 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 0d79bb02a4a..9a3a52895c3 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 d66be29f8b7..8f62cf4beb5 100644 --- a/homeassistant/components/modem_callerid/__init__.py +++ b/homeassistant/components/modem_callerid/__init__.py @@ -8,7 +8,7 @@ from homeassistant.exceptions import ConfigEntryNotReady 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 00000000000..63a88a8a4e5 --- /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 94c7c51ee80..3a1af4aa0a8 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 homeassistant.helpers import entity_platform 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_setup_entry( 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 c4e42a7be5b..12860bc6b9a 100644 --- a/homeassistant/components/renault/renault_vehicle.py +++ b/homeassistant/components/renault/renault_vehicle.py @@ -153,4 +153,9 @@ COORDINATORS: tuple[RenaultCoordinatorDescription, ...] = ( 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 8e63d09b5c7..c6621b16bbc 100644 --- a/homeassistant/components/renault/sensor.py +++ b/homeassistant/components/renault/sensor.py @@ -12,6 +12,7 @@ from renault_api.kamereon.models import ( KamereonVehicleCockpitData, KamereonVehicleHvacStatusData, KamereonVehicleLocationData, + KamereonVehicleResStateData, ) from homeassistant.components.sensor import ( @@ -333,4 +334,19 @@ SENSOR_TYPES: tuple[RenaultSensorEntityDescription, ...] = ( 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 89c6c364860..6c62e5d22e2 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 368f3b97fbd..41a72c6b7ab 100644 --- a/tests/components/renault/const.py +++ b/tests/components/renault/const.py @@ -229,6 +229,17 @@ MOCK_VEHICLES = { 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 @@ MOCK_VEHICLES = { "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 @@ MOCK_VEHICLES = { 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 @@ MOCK_VEHICLES = { "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 @@ MOCK_VEHICLES = { 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 @@ MOCK_VEHICLES = { "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 @@ MOCK_VEHICLES = { 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 00000000000..e6973ed091a --- /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 1a61859ac93..fccf5a757b4 100644 --- a/tests/components/renault/test_diagnostics.py +++ b/tests/components/renault/test_diagnostics.py @@ -157,6 +157,7 @@ VEHICLE_DATA = { "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 434c95b03b2..b5549c1b5e4 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -125,6 +125,7 @@ SCHEMA_BACKUP_FULL = vol.Schema( 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 6b77a180c09..6186f222183 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 e006cf9d829..689ec138043 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 d9d766974fd..4d40e42a47e 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 @@ from .const import ( 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 @@ async def async_setup_entry( 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 3ad43098225..d17702ec24f 100644 --- a/homeassistant/components/mobile_app/device_action.py +++ b/homeassistant/components/mobile_app/device_action.py @@ -7,6 +7,7 @@ from homeassistant.components import notify 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 67f3672ef8d..5e2ae23af16 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 0c26533b7cb..0cb6cfc6fcc 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 @@ from homeassistant.const import ( 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 c4e0a81560b..b7d38357a78 100644 --- a/homeassistant/components/mobile_app/helpers.py +++ b/homeassistant/components/mobile_app/helpers.py @@ -60,7 +60,7 @@ def setup_encrypt() -> tuple[int, Callable]: 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 a80e7db204e..20fa25ec21f 100644 --- a/homeassistant/components/mobile_app/http_api.py +++ b/homeassistant/components/mobile_app/http_api.py @@ -4,6 +4,7 @@ from __future__ import annotations 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 @@ class RegistrationsView(HomeAssistantView): # 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 f3852895d32..0ef9739c8bd 100644 --- a/homeassistant/components/mobile_app/push_notification.py +++ b/homeassistant/components/mobile_app/push_notification.py @@ -28,7 +28,7 @@ class PushChannel: 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 32477ccb50a..45bc4acd6a2 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 @@ from .const import ( 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 @@ async def async_setup_entry( 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 adb0cb25297..3f8386a2a27 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 8d542290458..ef29562578e 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -94,13 +94,6 @@ IGNORED_MODULES: Final[list[str]] = [ "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 9a3a52895c3..5ce91028102 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 2f6ea3c20cb..6c4bdd07b31 100644 --- a/homeassistant/components/sonos/const.py +++ b/homeassistant/components/sonos/const.py @@ -159,13 +159,16 @@ SONOS_CREATE_MEDIA_PLAYER = "sonos_create_media_player" 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 00000000000..85d15680a97 --- /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 7319139d3c2..65e8c4111ae 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -23,6 +23,7 @@ from homeassistant.components.media_player import ( 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 @@ from .const import ( 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 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity): 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 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity): 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 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity): 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 018eab4ca04..3a2bac51684 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -9,15 +9,12 @@ from functools import partial 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 @@ from .const import ( 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 @@ SUBSCRIPTION_SERVICES = [ "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 @@ class SonosSpeaker: 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 @@ class SonosSpeaker: 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 @@ class SonosSpeaker: """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 70061e88692..8e133f76ac1 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 cb53fb43ed2..96b3d222dc6 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 3692e50d595..32c54c2b290 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -16,6 +16,9 @@ from zwave_js_server.const import ( 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 @@ DISCOVERY_SCHEMAS = [ 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 00000000000..b94c7a8e2a3 --- /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 d73daacdc75..aea895d03bb 100644 --- a/tests/components/zwave_js/common.py +++ b/tests/components/zwave_js/common.py @@ -37,5 +37,7 @@ ID_LOCK_CONFIG_PARAMETER_SENSOR = ( 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 d8fef11269c..318dc99a1df 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 00000000000..ba55aadd98c --- /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 00000000000..6e76fe8d164 --- /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 d2c6b7eeaf3..4d3df8f00ca 100644 --- a/homeassistant/components/mpd/media_player.py +++ b/homeassistant/components/mpd/media_player.py @@ -119,6 +119,9 @@ class MpdDevice(MediaPlayerEntity): 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 @@ class MpdDevice(MediaPlayerEntity): """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 @@ class MpdDevice(MediaPlayerEntity): @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 @@ class MpdDevice(MediaPlayerEntity): 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 df780d7b39c..26d32589c94 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 d3044a7bf88..42ef52313ae 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 f85a5a6f779..17ca84b70fb 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 20fa25ec21f..ea8c56d1a7c 100644 --- a/homeassistant/components/mobile_app/http_api.py +++ b/homeassistant/components/mobile_app/http_api.py @@ -4,10 +4,8 @@ from __future__ import annotations 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 @@ class RegistrationsView(HomeAssistantView): 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 4723a2a6fb9..6cb4e964c9b 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 472202859e2..366d26ac0a7 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 42ef52313ae..c6fa19cea2d 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 17ca84b70fb..b4225458e8c 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 7d5736a2e68..0154e2eba28 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 @@ async def async_setup_entry( # noqa: C901 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 4f6f380e47d..a2541c064c0 100644 --- a/homeassistant/components/mqtt/tag.py +++ b/homeassistant/components/mqtt/tag.py @@ -177,7 +177,10 @@ class MQTTTagScanner: 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 d2bb647b2ca..4d964d9f08a 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 8a57a34b739..c4414cdbefd 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 .const import MOCK_CONFIG 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 | 206 ++++++++---------- tests/components/greeneye_monitor/common.py | 10 + .../components/greeneye_monitor/test_init.py | 13 +- .../greeneye_monitor/test_sensor.py | 84 ++++--- 4 files changed, 165 insertions(+), 148 deletions(-) diff --git a/homeassistant/components/greeneye_monitor/sensor.py b/homeassistant/components/greeneye_monitor/sensor.py index dee2dbe75d9..d6b7185a7cf 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] + monitor_configs = discovery_info[CONF_MONITORS] - 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], + 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 @@ class GEMSensor(SensorEntity): 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 @@ class PulseCounter(GEMSensor): 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 @@ class PulseCounter(GEMSensor): ) @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 0a19b79795f..e9285647f4d 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 c8e13714939..df8c67e7eed 100644 --- a/tests/components/greeneye_monitor/test_init.py +++ b/tests/components/greeneye_monitor/test_init.py @@ -18,6 +18,7 @@ from .common import ( 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 ac1fe92873a..f739b8a64ca 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 @@ from .common import ( 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 ) - assert len(monitors.listeners) == 1 - await disable_entity(hass, "sensor.voltage_1") - assert len(monitors.listeners) == 0 # Make sure we cleaned up the listener + entity_registry = get_entity_registry(hass) + assert entity_registry.async_get("sensor.voltage_1") is None -async def test_updates_state_when_monitor_connected( +async def test_sensors_created_when_monitor_connected( hass: HomeAssistant, monitors: AsyncMock ) -> None: - """Test that a sensor updates its state when its monitor first connects.""" + """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_sensor_state(hass, "sensor.voltage_1", STATE_UNKNOWN) assert len(monitors.listeners) == 1 - connect_monitor(monitors, SINGLE_MONITOR_SERIAL_NUMBER) + 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_sensors_created_during_setup_if_monitor_already_connected( + hass: HomeAssistant, monitors: AsyncMock +) -> None: + """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 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 5ce91028102..696cd5ce3b0 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 677c0d4cc2a..9e9cf1d6ca4 100644 --- a/homeassistant/components/nanoleaf/__init__.py +++ b/homeassistant/components/nanoleaf/__init__.py @@ -6,16 +6,32 @@ from dataclasses import dataclass 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_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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 505af8ce69d..7e246209733 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 00000000000..311d12506ba --- /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 96fcfd2622a..80eb2ded7d0 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 7696f056aa3..7064c617b0e 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 696cd5ce3b0..360bd5f6911 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 00000000000..a94c731ec8f --- /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 00000000000..0e98b7c7e21 --- /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 00000000000..8756b768e52 --- /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 60a9163df5f..7f031ef3b6a 100644 --- a/homeassistant/components/overkiz/const.py +++ b/homeassistant/components/overkiz/const.py @@ -21,6 +21,7 @@ UPDATE_INTERVAL_ALL_ASSUMED_STATE: Final = timedelta(minutes=60) PLATFORMS: list[Platform] = [ Platform.BINARY_SENSOR, Platform.BUTTON, + Platform.CLIMATE, Platform.COVER, Platform.LIGHT, Platform.LOCK, @@ -57,6 +58,7 @@ OVERKIZ_DEVICE_TO_PLATFORM: dict[UIClass | UIWidget, Platform | None] = { 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 65af74981cf..9be01f27e4d 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 @@ class DerivativeSensor(RestoreEntity, SensorEntity): 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 @@ class DerivativeSensor(RestoreEntity, SensorEntity): ): 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 @@ class DerivativeSensor(RestoreEntity, SensorEntity): ) 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 03861a30c47..b93aef50774 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 fd7e731194c..b87ddc670c3 100644 --- a/homeassistant/components/telegram/notify.py +++ b/homeassistant/components/telegram/notify.py @@ -11,6 +11,11 @@ from homeassistant.components.notify import ( 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 @@ class TelegramNotificationService(BaseNotificationService): 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 64ef4f0939f..5faf6941aeb 100644 --- a/homeassistant/components/lg_netcast/media_player.py +++ b/homeassistant/components/lg_netcast/media_player.py @@ -20,6 +20,7 @@ from homeassistant.components.media_player.const import ( SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, + SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP, ) from homeassistant.const import ( @@ -48,6 +49,7 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) SUPPORT_LGTV = ( SUPPORT_PAUSE | SUPPORT_VOLUME_STEP + | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK @@ -121,11 +123,8 @@ class LgTVDevice(MediaPlayerEntity): 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 @@ class LgTVDevice(MediaPlayerEntity): 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 @@ class LgTVDevice(MediaPlayerEntity): """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 7c03ecab271..cebdd4f4573 100644 --- a/homeassistant/components/telegram_bot/__init__.py +++ b/homeassistant/components/telegram_bot/__init__.py @@ -63,6 +63,7 @@ ATTR_PASSWORD = "password" 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_FILE = BASE_SERVICE_SCHEMA.extend( } ) +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_SCHEMA_LEAVE_CHAT = vol.Schema({vol.Required(ATTR_CHAT_ID): vol.Coerce(i 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 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: ) elif msgtype in [ SERVICE_SEND_PHOTO, - SERVICE_SEND_STICKER, SERVICE_SEND_ANIMATION, SERVICE_SEND_VIDEO, SERVICE_SEND_VOICE, @@ -380,6 +384,10 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: 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 @@ class TelegramNotificationService: 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 ea406cfdf96..6afd42dffb8 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 3f40668fdd5..7c45d193bbc 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 c6fa19cea2d..100e1c7bcc9 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 b4225458e8c..876d70eb1a6 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 92697378293..d6c9fffdfa7 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 00000000000..61c28dc9992 --- /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 d53fecffa63..b49e436ef8b 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 ebdd7ed4105..d0a65e190f0 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.const import STATE_OFF, STATE_ON 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 @@ ENTRY_DATA_NO_CORONA: dict[str, Any] = { 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 455d7465a87..66e8edb2806 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.config_entries import ConfigEntryState 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 @@ ENTRY_DATA: dict[str, Any] = { 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 cd39afd9696..2e9b0080815 100644 --- a/homeassistant/components/overkiz/executor.py +++ b/homeassistant/components/overkiz/executor.py @@ -4,12 +4,22 @@ from __future__ import annotations 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 @@ class OverkizExecutor: 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 0bf4bb80a18..4d3c3046a89 100644 --- a/homeassistant/components/mysensors/__init__.py +++ b/homeassistant/components/mysensors/__init__.py @@ -15,6 +15,7 @@ from homeassistant.config_entries import ConfigEntry 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 0ced1520758..b1e562e878c 100644 --- a/homeassistant/components/mysensors/device.py +++ b/homeassistant/components/mysensors/device.py @@ -65,10 +65,6 @@ class MySensorsDevice: """ 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 @@ class MySensorsDevice: 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 6dd7add37e6..fe98b3f7e0a 100644 --- a/tests/components/mysensors/conftest.py +++ b/tests/components/mysensors/conftest.py @@ -6,6 +6,7 @@ import json 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 @@ async def integration( 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 7c83334d8f3..6f97c312ec0 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 @@ from homeassistant.components.mysensors.const import ( 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 6700e66a941..2dcfe639a64 100644 --- a/homeassistant/components/rfxtrx/__init__.py +++ b/homeassistant/components/rfxtrx/__init__.py @@ -5,7 +5,6 @@ import asyncio import binascii from collections.abc import Callable import copy -import functools import logging from typing import NamedTuple @@ -25,9 +24,13 @@ from homeassistant.const import ( 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 @@ from .const import ( 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 @@ PLATFORMS = [ ] -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 @@ async def async_setup_internal(hass, entry: config_entries.ConfigEntry): 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 @@ class RfxtrxEntity(RestoreEntity): ) ) - 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 1ec74c2415a..7a842ad470c 100644 --- a/homeassistant/components/rfxtrx/config_flow.py +++ b/homeassistant/components/rfxtrx/config_flow.py @@ -23,7 +23,6 @@ from homeassistant.const import ( 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 @@ from .const import ( 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 @@ class OptionsFlow(config_entries.OptionsFlow): ] 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 @@ class OptionsFlow(config_entries.OptionsFlow): 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 @@ class OptionsFlow(config_entries.OptionsFlow): ): 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 b7cb52df984..50cd355c457 100644 --- a/homeassistant/components/rfxtrx/const.py +++ b/homeassistant/components/rfxtrx/const.py @@ -6,7 +6,6 @@ CONF_SIGNAL_REPETITIONS = "signal_repetitions" 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 eb3a9ba699c..542ff9a45cd 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 06e37545d25..70de0be5937 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 rfxtrx_fixture(hass): @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 cd87f02c893..bee9ea4880a 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 4ad5f9a342a..d5562fa5149 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 @@ async def test_fire_event(hass, rfxtrx): 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 32c54c2b290..69a3d05539b 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -16,6 +16,7 @@ from zwave_js_server.const import ( 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 @@ DISCOVERY_SCHEMAS = [ }, ), ), + # 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 caba0f5de36..0f3f17df41a 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 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): # 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 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): 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 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): 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 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): 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 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): 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 318dc99a1df..2535daaf114 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 00000000000..ea267d86b8c --- /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 373ca2525ac..01de5a70692 100644 --- a/tests/components/zwave_js/test_light.py +++ b/tests/components/zwave_js/test_light.py @@ -13,9 +13,17 @@ from homeassistant.components.light import ( 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 @@ from .common import ( 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 | 63 ++++++++--- .../components/tasmota/device_trigger.py | 4 +- homeassistant/components/tasmota/discovery.py | 16 +-- tests/components/tasmota/test_discovery.py | 107 +++++++++++++++++- 4 files changed, 161 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/tasmota/__init__.py b/homeassistant/components/tasmota/__init__.py index 2d664bb46ee..44dd2489177 100644 --- a/homeassistant/components/tasmota/__init__.py +++ b/homeassistant/components/tasmota/__init__.py @@ -26,6 +26,7 @@ from homeassistant.components.mqtt.subscription import ( 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 async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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"]] + connections: set[tuple[str, str]] + if event.data["action"] == "update": + if "config_entries" not in event.data["changes"]: + return - if entry.entry_id not in device.config_entries: - 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 device.connections if c[0] == CONNECTION_NETWORK_MAC] + 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 61efbb76e23..aca5a2848e3 100644 --- a/homeassistant/components/tasmota/device_trigger.py +++ b/homeassistant/components/tasmota/device_trigger.py @@ -20,7 +20,7 @@ from homeassistant.config_entries import ConfigEntry 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 async_setup_trigger( 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 67aea199fe4..da9e809bd8b 100644 --- a/homeassistant/components/tasmota/discovery.py +++ b/homeassistant/components/tasmota/discovery.py @@ -21,7 +21,7 @@ from hatasmota.sensor import TasmotaBaseSensorConfig 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 async_start( """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_start( 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_start( """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_start( 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 713d0f5ae67..90ca5d918fd 100644 --- a/tests/components/tasmota/test_discovery.py +++ b/tests/components/tasmota/test_discovery.py @@ -10,7 +10,7 @@ from homeassistant.helpers import device_registry as dr 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 b241e07fc89..60ea4b3284b 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -198,7 +198,6 @@ class UniFiClientTracker(UniFiClient, ScannerEntity): 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 b490d43fffd..532f19c35ae 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 74a255a7f96..621c6c315fc 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 bb81d1fb04f..61c36da6e90 100644 --- a/homeassistant/auth/mfa_modules/__init__.py +++ b/homeassistant/auth/mfa_modules/__init__.py @@ -16,7 +16,7 @@ from homeassistant.data_entry_flow import FlowResult 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 d80d7a5273b..63389059051 100644 --- a/homeassistant/auth/providers/__init__.py +++ b/homeassistant/auth/providers/__init__.py @@ -25,7 +25,7 @@ from ..models import Credentials, RefreshToken, User, UserMeta _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 1ab24927bcb..5ecd326afb6 100644 --- a/homeassistant/components/alexa/entities.py +++ b/homeassistant/components/alexa/entities.py @@ -83,7 +83,7 @@ if TYPE_CHECKING: _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 f3f669de3b3..a27bc432b4f 100644 --- a/homeassistant/components/alexa/handlers.py +++ b/homeassistant/components/alexa/handlers.py @@ -73,7 +73,7 @@ from .errors import ( 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 0b8bf55fcda..7352bbd995a 100644 --- a/homeassistant/components/alexa/intent.py +++ b/homeassistant/components/alexa/intent.py @@ -12,7 +12,7 @@ from .const import DOMAIN, SYN_RESOLUTION_MATCH _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 d2ad3ec313c..a5b54a621a7 100644 --- a/homeassistant/components/filter/sensor.py +++ b/homeassistant/components/filter/sensor.py @@ -52,7 +52,7 @@ FILTER_NAME_OUTLIER = "outlier" 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 80bc61cc61d..5f38194e3e3 100644 --- a/homeassistant/components/google_assistant/smart_home.py +++ b/homeassistant/components/google_assistant/smart_home.py @@ -19,7 +19,7 @@ from .helpers import GoogleEntity, RequestData, async_get_entities 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 922c4c52568..d348b4c1f42 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 @@ SWITCH_TYPES = { 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 ef878fc6f2b..af784750627 100644 --- a/homeassistant/components/konnected/handlers.py +++ b/homeassistant/components/konnected/handlers.py @@ -9,7 +9,7 @@ from homeassistant.util import decorator 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 d659d7625c1..221c4eef733 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -109,7 +109,7 @@ _LOGGER = logging.getLogger(__name__) 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 b167c8c58de..be0381ab74e 100644 --- a/homeassistant/components/mysensors/gateway.py +++ b/homeassistant/components/mysensors/gateway.py @@ -3,7 +3,7 @@ from __future__ import annotations 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 _gw_callback_factory( _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 4d61a2812ae..57ff12fc6f0 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 .const import CHILD_CALLBACK, NODE_CALLBACK, DevId, GatewayId 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 5b6682393b1..a5f67111738 100644 --- a/homeassistant/components/mysensors/helpers.py +++ b/homeassistant/components/mysensors/helpers.py @@ -31,7 +31,9 @@ from .const import ( ) _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 9574d44edea..b518dbbb451 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 98031926cfd..d90a52ae409 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 homeassistant.util.decorator import Registry 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 bd01284329b..b85a37dadf9 100644 --- a/homeassistant/components/owntracks/messages.py +++ b/homeassistant/components/owntracks/messages.py @@ -17,7 +17,7 @@ from .helper import supports_encryption _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 e22e06df7e2..157f20b5b37 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -245,7 +245,7 @@ class Stream: 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 @@ class Stream: 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 91414dd96d9..8db6a239818 100644 --- a/homeassistant/components/stream/core.py +++ b/homeassistant/components/stream/core.py @@ -23,7 +23,7 @@ if TYPE_CHECKING: 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 af04ee032dc..7bc37bcd305 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -62,7 +62,7 @@ SOURCE_UNIGNORE = "unignore" # 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 @@ class ConfigEntry: ) 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 @@ class ConfigEntriesFlowManager(data_entry_flow.FlowManager): 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 @@ class OptionsFlowManager(data_entry_flow.FlowManager): 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 38fe621f96c..f280feb83b2 100644 --- a/homeassistant/helpers/selector.py +++ b/homeassistant/helpers/selector.py @@ -13,7 +13,7 @@ from homeassistant.util import decorator 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 602cdba5598..c648f6f1cab 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 3f8386a2a27..781bc5c199e 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 9a3db7f8294..ad728fd3604 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 366d26ac0a7..176e25c414f 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 100e1c7bcc9..395b221ea61 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 876d70eb1a6..bf79d2d991c 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 49f7ed18490..af372ebd8e8 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from .backports.enum import StrEnum 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 74ea6e296b5..1943703d33d 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 f1a94cbbe20..a39b03fbd9f 100644 --- a/homeassistant/components/wolflink/sensor.py +++ b/homeassistant/components/wolflink/sensor.py @@ -152,10 +152,11 @@ class WolfLinkState(WolfLinkSensor): 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 e2bd1b4903b..77b254dcf9d 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 e36ccdac931..319ef295be3 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 c4741672186..db1a79145b4 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 85d15680a97..f4108b85317 100644 --- a/homeassistant/components/sonos/media.py +++ b/homeassistant/components/sonos/media.py @@ -169,7 +169,8 @@ class SonosMedia: 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 1c8e87051be..1c8514d0d26 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 @@ class SQLSensor(SensorEntity): _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 650013dda7f..4ed0a9ada96 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -34,6 +34,10 @@ from homeassistant.helpers.json import ExtendedJSONEncoder 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 130870f73f0..742d9bddd38 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 336c79d22b8..c3564d2b21b 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 4ed0a9ada96..abc37dd2a0a 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -480,7 +480,9 @@ async def handle_subscribe_trigger( 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 9428d6fd87d..6c5615ad253 100644 --- a/homeassistant/components/websocket_api/const.py +++ b/homeassistant/components/websocket_api/const.py @@ -53,4 +53,6 @@ SIGNAL_WEBSOCKET_DISCONNECTED: Final = "websocket_disconnected" # 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 3ec156e6949..618879f4b7f 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 9c43bfed282..6a9b00d2041 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 395b221ea61..9cf25c8e2f5 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 bf79d2d991c..2d577e3d8a2 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 b60ea3a576c..25193019f7d 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 9cf25c8e2f5..7dfc9c19b9e 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 2d577e3d8a2..cc71923136d 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 48ada7b14a2..93cd1be3204 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 7dfc9c19b9e..9b21741348f 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 cc71923136d..93dacff8619 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 af372ebd8e8..72968b5021d 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from .backports.enum import StrEnum 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 1943703d33d..3f032e34de3 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 c7652fe97b9..1982d1f3df5 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -179,6 +179,40 @@ MQTT_WILL_BIRTH_SCHEMA = vol.Schema( 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 @@ CONFIG_SCHEMA = vol.Schema( 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 @@ class MQTT: 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 9981b3a1109..bde540d6372 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 8b0dd26fc32..04a26f2822b 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 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 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 @@ async def async_wait_for_elk_to_sync( 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 2453958b3de..a21cf186005 100644 --- a/homeassistant/components/elkm1/config_flow.py +++ b/homeassistant/components/elkm1/config_flow.py @@ -24,6 +24,7 @@ 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 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 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 80d594fce0a..fd4856bd5d5 100644 --- a/homeassistant/components/elkm1/const.py +++ b/homeassistant/components/elkm1/const.py @@ -9,7 +9,7 @@ from homeassistant.const import ATTR_CODE, CONF_ZONE 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 7055f3958e9..326698c3686 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 d8a0feea670..49402d7b4d5 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 feee4b6e903..84cfc2e69fd 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 0eb4dc57d9d..5c1dbb376a0 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 6e7ac1257fa..6968b401561 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 ddc50fb7702..6c0e46b34c6 100644 --- a/tests/components/motioneye/test_media_source.py +++ b/tests/components/motioneye/test_media_source.py @@ -12,6 +12,7 @@ from homeassistant.components.media_source.models import PlayMedia 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 @@ TEST_IMAGES = { _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 015a14fb92c..2361049ecc1 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 193d2b7fa81..bf6e7f06abd 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 9b21741348f..9541447aaf0 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 93dacff8619..e244a67ffe1 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 ad728fd3604..f9ad2bd428d 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 176e25c414f..1bd905e6abd 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 9541447aaf0..bf43d648d5d 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 e244a67ffe1..8484a2c12d3 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 72968b5021d..925c08e24fd 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from .backports.enum import StrEnum 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 3f032e34de3..295206ed003 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 9885ccb54ef..1eb6f859d0c 100644 --- a/homeassistant/components/abode/camera.py +++ b/homeassistant/components/abode/camera.py @@ -88,6 +88,8 @@ class AbodeCamera(AbodeDevice, Camera): 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 0cdeb813945..64feb17d6b5 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 17f1c8586c0..ef68ba94518 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 b3357d331bd..771f88f0ef1 100644 --- a/homeassistant/components/twitch/sensor.py +++ b/homeassistant/components/twitch/sensor.py @@ -3,12 +3,18 @@ from __future__ import annotations 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 @@ ICON = "mdi:twitch" 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 - add_entities([TwitchSensor(channel_id, client) for channel_id in channel_ids], True) + channels = client.get_users(logins=channels) + + 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 @@ class TwitchSensor(SensorEntity): @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 @@ class TwitchSensor(SensorEntity): """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 @@ class TwitchSensor(SensorEntity): @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 @@ class TwitchSensor(SensorEntity): 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 bf43d648d5d..82f12c9c57e 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 8484a2c12d3..aa09fe71623 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 310be91c796..bfffeb4ae7f 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 @@ CONFIG = { sensor.DOMAIN: { "platform": "twitch", CONF_CLIENT_ID: "1234", + CONF_CLIENT_SECRET: " abcd", "channels": ["channel123"], } } @@ -20,39 +18,46 @@ CONFIG_WITH_OAUTH = { 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 29c7cb786e6..ed3476c4576 100644 --- a/homeassistant/components/nanoleaf/light.py +++ b/homeassistant/components/nanoleaf/light.py @@ -153,11 +153,17 @@ class NanoleafLight(NanoleafEntity, LightEntity): 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 @@ class NanoleafLight(NanoleafEntity, LightEntity): 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 71068479fe4..27d51d8f3e6 100644 --- a/homeassistant/components/sonos/__init__.py +++ b/homeassistant/components/sonos/__init__.py @@ -182,6 +182,9 @@ class SonosDiscoveryManager: 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 @@ class SonosDiscoveryManager: 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 @@ class SonosDiscoveryManager: ) 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 1b0c5d521c9..e0fdb0f73fd 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 3a39a7870a3..86034ea9cfc 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 5067ee84826..56b0abb6a15 100644 --- a/homeassistant/components/kodi/media_player.py +++ b/homeassistant/components/kodi/media_player.py @@ -5,6 +5,7 @@ from datetime import timedelta from functools import wraps import logging import re +from typing import Any import urllib.parse import jsonrpc_base @@ -12,7 +13,11 @@ from jsonrpc_base.jsonrpc import ProtocolError, TransportError 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 @@ from homeassistant.components.media_player.const import ( 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 @@ class KodiEntity(MediaPlayerEntity): 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 @@ class KodiEntity(MediaPlayerEntity): 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 @@ class KodiEntity(MediaPlayerEntity): {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 @@ class KodiEntity(MediaPlayerEntity): ) 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 871cd27166d..e3b97e9385b 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 82f12c9c57e..713cd7dd33e 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 aa09fe71623..3f94c277f31 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 5a0a7bbf29f..5d294931e66 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -293,17 +293,19 @@ async def async_setup_entry( # noqa: C901 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 2535daaf114..9696c922fb3 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 @@ def mock_client_fixture(controller_state, version_state, log_config_state): 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 1eb2baf3a02..bf547556ac8 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 646363e3313..cbd11c66870 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 5616abd6e0f..59e4fdfc9fb 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 668d1b9dce9..b0dba3c6d05 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 a726175fa38..2b8477b597f 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 8574674714f..206a32df664 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 afa216cac32..1241e0b35d7 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 2526e346a53..8c655d503ed 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 50886b504a7..75d8bb99e55 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 98c185fd8d5..0ac4c6ab696 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 52f2168fd83..8bfe3a3f7af 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 f940dd210aa..398371a7445 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 3805394dbce..b81acf66b80 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 5feaa247f2e..5c8a12a6832 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 00000000000..1f3c71971bc --- /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 b5373f38ec4..7959378a7ad 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 59dff945846..6d4defbd42c 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 42200c2f1d6..4d10577a2d1 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 bde7c90e1e4..913f24d41ae 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 a0ccd4de9c5..47aafdfd0a4 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 3edb0494c37..a1806a99ce0 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 ac32a9f99bb..444b7eafc67 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 ddbff0f3ffa..fa4c96d439a 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 d09848fb759..fc89976d14a 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 f83a1193c22..edab052af5b 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 d8973f2688e..8c88082718c 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 fb31f838667..aa0e05dd47f 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 d47896a980a..4cbf9ef1ce4 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 d27338db5a9..926285e5359 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 bfa56891413..11e88eff8be 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 213b873f85c..b244913070c 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 b22c21e4777..642682766df 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 f5e66b7e7a6..5bd4cfc8080 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 2b4a3a88984..dc6e9e40d7c 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 fedee0f9cf1..64f83a43e0d 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 598650b863c..39c04216a04 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 a3b34aeedf0..a99303af259 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 fe63eaee207..8905e47b155 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 a2fdaa99561..d1db5664f76 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 d37e82ea3af..f7abbffb590 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 f5560dd7e78..af5314002fa 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 7407607e086..3b1278da0b9 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 f892eb5570e..272f6118830 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 8de7dd2b713..e652653d946 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 9b5ac66b06f..29858e0eb97 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 1003316f1e5..1b3a2d1204f 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 891a417551e..1d41e145a95 100644 --- a/tests/components/zwave_js/test_sensor.py +++ b/tests/components/zwave_js/test_sensor.py @@ -18,6 +18,7 @@ from homeassistant.components.zwave_js.const import ( 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 ee653534fc8..e7340040658 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 6be35e0102b..d978b377cc5 100644 --- a/homeassistant/components/tuya/switch.py +++ b/homeassistant/components/tuya/switch.py @@ -137,6 +137,16 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = { 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 3a1af4aa0a8..f4b2f3c3e44 100644 --- a/homeassistant/components/modem_callerid/sensor.py +++ b/homeassistant/components/modem_callerid/sensor.py @@ -44,13 +44,7 @@ async def async_setup_entry( ) 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 @@ class ModemCalleridSensor(SensorEntity): 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 73a094fd95a..204b5d0aebd 100644 --- a/homeassistant/components/zwave_js/migrate.py +++ b/homeassistant/components/zwave_js/migrate.py @@ -9,7 +9,7 @@ from zwave_js_server.client import Client as ZwaveClient 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 @@ CC_ID_LABEL_TO_PROPERTY = { 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 @@ class LegacyZWaveMigration: # 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 3479638b387..95f969a9586 100644 --- a/tests/components/zwave_js/test_migrate.py +++ b/tests/components/zwave_js/test_migrate.py @@ -8,6 +8,7 @@ from zwave_js_server.model.node import Node 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_NAME = "Z-Wave Multisensor Device" 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 6fe4683c1fc..26494e4c8a7 100644 --- a/homeassistant/components/media_player/browse_media.py +++ b/homeassistant/components/media_player/browse_media.py @@ -15,7 +15,9 @@ from .const import CONTENT_AUTH_EXPIRY_TIME, MEDIA_CLASS_DIRECTORY @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 77b254dcf9d..2bcd80a39ab 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 319ef295be3..2655000efc9 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 925c08e24fd..95f2b3d9f57 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from .backports.enum import StrEnum 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 295206ed003..00a0d4a695f 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 00000000000..2e061d35528 --- /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 00000000000..c3ed1b45592 --- /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 00000000000..56af8bafe06 --- /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 a48726211b2..bc8ab1c0215 100644 --- a/homeassistant/components/powerwall/sensor.py +++ b/homeassistant/components/powerwall/sensor.py @@ -3,7 +3,7 @@ from __future__ import annotations 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 @@ from .models import PowerwallData, PowerwallRuntimeData _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 - ] = [] - 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, - ) - ) + PowerWallEnergySensor + | PowerWallImportSensor + | PowerWallExportSensor + | PowerWallChargeSensor + ] = [PowerWallChargeSensor(powerwall_data)] - entities.append(PowerWallChargeSensor(powerwall_data)) + for meter in data.meters.meters: + entities.extend( + [ + PowerWallEnergySensor(powerwall_data, meter), + PowerWallExportSensor(powerwall_data, meter), + PowerWallImportSensor(powerwall_data, meter), + ] + ) async_add_entities(entities) @@ -128,18 +127,54 @@ class PowerWallEnergyDirectionSensor(PowerWallEntity, SensorEntity): """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 c61cbef146a..ab7661fefe2 100644 --- a/homeassistant/components/camera/media_source.py +++ b/homeassistant/components/camera/media_source.py @@ -81,11 +81,13 @@ class CameraMediaSource(MediaSource): # 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 @@ class CameraMediaSource(MediaSource): 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 2bcd80a39ab..3c42016f8f7 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 3a3558419e5..54d6ef6279e 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 2655000efc9..491b1972cb6 100644 --- a/tests/components/media_source/test_init.py +++ b/tests/components/media_source/test_init.py @@ -6,7 +6,7 @@ import yarl 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 2272ceb183f..b2d881e8bf2 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 7bea86d323c..d739c571c8b 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 fb21e930efd..58afb5c944a 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 e0fdb0f73fd..519c4dc7c1b 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 d8cd540e613..72b572e8d3e 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 e9548c03ba6..d5d0c2c0370 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 1bd905e6abd..fd8fe9c0681 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 713cd7dd33e..a1ed674536c 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 3f94c277f31..5f9291dc2e1 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 ab7661fefe2..e65aabe459d 100644 --- a/homeassistant/components/camera/media_source.py +++ b/homeassistant/components/camera/media_source.py @@ -73,10 +73,7 @@ class CameraMediaSource(MediaSource): 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 @@ class CameraMediaSource(MediaSource): 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 @@ class CameraMediaSource(MediaSource): 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 54d6ef6279e..b9fb22c9ed8 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_filter_non_hls(hass, mock_camera_web_rtc): +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_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 519c4dc7c1b..73247d23a9d 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 56b0abb6a15..53798a7ccd9 100644 --- a/homeassistant/components/kodi/media_player.py +++ b/homeassistant/components/kodi/media_player.py @@ -77,7 +77,12 @@ from homeassistant.helpers.network import is_internal_request 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 @@ class KodiEntity(MediaPlayerEntity): 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 06264153af2..502ff453a27 100644 --- a/homeassistant/components/doorbird/__init__.py +++ b/homeassistant/components/doorbird/__init__.py @@ -3,6 +3,7 @@ from __future__ import annotations 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 @@ class ConfiguredDoorBird: """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 @@ class ConfiguredDoorBird: 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 @@ class ConfiguredDoorBird: 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 18d5549ef9a..13a08685c68 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 a1ed674536c..9984b08e920 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 a3de0e51708..0b5f8a49a34 100644 --- a/homeassistant/components/dhcp/__init__.py +++ b/homeassistant/components/dhcp/__init__.py @@ -159,7 +159,7 @@ class WatcherBase: 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 @@ class WatcherBase: ).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 @@ class DeviceTrackerRegisteredWatcher(WatcherBase): 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 d1b8d72be67..a809d6eb5ab 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 f9ad2bd428d..4b28744d0e3 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 fd8fe9c0681..a36f21efd6b 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 9984b08e920..626406a0bff 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 5f9291dc2e1..964a793b9c1 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 95f2b3d9f57..b3f86ff9d6a 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from .backports.enum import StrEnum 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 00a0d4a695f..7cacc226032 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 c6812f4d9de..803b093fd40 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -7,7 +7,7 @@ import json 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 1c5d6b1b685..5e8fe27e21e 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 626406a0bff..f04552f55d1 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 964a793b9c1..49995b5de6a 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 773142a0109..9f387858e5f 100644 --- a/homeassistant/components/picnic/coordinator.py +++ b/homeassistant/components/picnic/coordinator.py @@ -112,7 +112,7 @@ class PicnicUpdateCoordinator(DataUpdateCoordinator): 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 133ad4f2bda..327d5973892 100644 --- a/homeassistant/components/alexa/capabilities.py +++ b/homeassistant/components/alexa/capabilities.py @@ -822,6 +822,8 @@ class AlexaInputController(AlexaCapability): """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 8a9a40e3217..d24849e1006 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 238c25ad917..85a060ae7cd 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 f04552f55d1..e8ace669520 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 49995b5de6a..770acbd7d61 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 1982d1f3df5..107bc4660c2 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -967,10 +967,6 @@ class MQTT: 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 @@ class MQTT: 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 e589e447a01..7296d4e8101 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 fbc1d2642ea..a11847d2b0c 100644 --- a/homeassistant/components/sonos/helpers.py +++ b/homeassistant/components/sonos/helpers.py @@ -3,7 +3,7 @@ from __future__ import annotations 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 soco_error( ) 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 soco_error( 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 fa3b8688c47..dc4c7c001ae 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 a36f21efd6b..c944b6c141c 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 e8ace669520..99c1a711d14 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 770acbd7d61..1201b07682a 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 b3f86ff9d6a..e46509d2b84 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from .backports.enum import StrEnum 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 7cacc226032..58a3918d0ad 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 bc8ab1c0215..93b3c64d18c 100644 --- a/homeassistant/components/powerwall/sensor.py +++ b/homeassistant/components/powerwall/sensor.py @@ -114,7 +114,7 @@ class PowerWallEnergySensor(PowerWallEntity, SensorEntity): 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 @@ class PowerWallExportSensor(PowerWallEnergyDirectionSensor): @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 @@ class PowerWallImportSensor(PowerWallEnergyDirectionSensor): @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 868d9e3076d..fed47823c7f 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 8379845982a..1a9f3dc0314 100644 --- a/homeassistant/components/flipr/__init__.py +++ b/homeassistant/components/flipr/__init__.py @@ -3,6 +3,7 @@ from datetime import timedelta 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.entity import DeviceInfo, EntityDescription from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, DataUpdateCoordinator, + UpdateFailed, ) from .const import ATTRIBUTION, CONF_FLIPR_ID, DOMAIN, MANUFACTURER, NAME @@ -68,9 +70,15 @@ class FliprDataUpdateCoordinator(DataUpdateCoordinator): 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 357b5aeb160..77388393d3f 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 99c1a711d14..ef6c59a53ef 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 1201b07682a..85b53a3408c 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 7fd04fbc992..45816801472 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 9d7780ab900..a8c4b3cf458 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 79b996530e3..050814e3817 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 15a9b8d1ff8..0838375fd1f 100644 --- a/tests/helpers/test_network.py +++ b/tests/helpers/test_network.py @@ -20,6 +20,17 @@ from homeassistant.helpers.network import ( 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 d3a65448f84..1166d1db6db 100644 --- a/homeassistant/components/dlna_dms/dms.py +++ b/homeassistant/components/dlna_dms/dms.py @@ -575,7 +575,8 @@ class DmsDeviceSource: children=children, ) - media_source.calculate_children_class() + if media_source.children: + media_source.calculate_children_class() return media_source @@ -645,7 +646,8 @@ class DmsDeviceSource: 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 26494e4c8a7..fa825042817 100644 --- a/homeassistant/components/media_player/browse_media.py +++ b/homeassistant/components/media_player/browse_media.py @@ -3,6 +3,7 @@ from __future__ import annotations from datetime import timedelta import logging +from typing import Any from urllib.parse import quote import yarl @@ -74,11 +75,15 @@ class BrowseMedia: 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 @@ class BrowseMedia: 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 @@ class BrowseMedia: 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 663941de77a..40a1269557d 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 896968557c1..a9ac5946f30 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 6c0e46b34c6..6979d5c645d 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 4067110e336..63929740020 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 b2a88b00fe0..91be62e5ab7 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 4f910043d12..347e8d3fc72 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 3130484d10b..bdeef15971e 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 80a26a56b22..8825de877e5 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 ef6c59a53ef..672cdec1707 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 85b53a3408c..3c76d58854c 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 8d691b0e5c9..b10cd054ff9 100644 --- a/homeassistant/components/co2signal/sensor.py +++ b/homeassistant/components/co2signal/sensor.py @@ -92,14 +92,15 @@ class CO2Sensor(update_coordinator.CoordinatorEntity[CO2SignalResponse], SensorE 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 4b28744d0e3..cc118e23dc9 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 c944b6c141c..bd9e1da6a69 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 672cdec1707..4d6dcefb10f 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 3c76d58854c..c7828110fc2 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 ce9c57f5889..21a7447e2b2 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -215,6 +215,15 @@ SENSORS: Final = { 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 e46509d2b84..51af5c06d22 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from .backports.enum import StrEnum 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 58a3918d0ad..4d686e63c5b 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 | 74 ++++-- .../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, 344 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/mobile_app/const.py b/homeassistant/components/mobile_app/const.py index a2a4e15ee72..ba81a0484cf 100644 --- a/homeassistant/components/mobile_app/const.py +++ b/homeassistant/components/mobile_app/const.py @@ -28,6 +28,7 @@ ATTR_CONFIG_ENTRY_ID = "entry_id" 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 b7d38357a78..545c3511fc9 100644 --- a/homeassistant/components/mobile_app/helpers.py +++ b/homeassistant/components/mobile_app/helpers.py @@ -7,7 +7,7 @@ import json 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 @@ from .const import ( ATTR_DEVICE_NAME, ATTR_MANUFACTURER, ATTR_MODEL, + ATTR_NO_LEGACY_ENCRYPTION, ATTR_OS_VERSION, ATTR_SUPPORTS_ENCRYPTION, CONF_SECRET, @@ -34,7 +35,7 @@ from .const import ( _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() + keylen, encrypt = setup_encrypt( + HexEncoder if ATTR_NO_LEGACY_ENCRYPTION in registration else RawEncoder + ) - key = registration[CONF_SECRET].encode("utf-8") - key = key[:keylen] - key = key.ljust(keylen, b"\0") + 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 221c4eef733..860b8ef7b53 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -7,6 +7,7 @@ import logging 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 @@ from .const import ( 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 .const import ( ) 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 5d92418bba2..4c4e9b54ccf 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 48b61988de2..5f220cf0ebe 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 .const import CALL_SERVICE, FIRE_EVENT, REGISTER_CLEARTEXT, RENDER_TEMPLATE 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 7a842ad470c..549a5c3ccbf 100644 --- a/homeassistant/components/rfxtrx/config_flow.py +++ b/homeassistant/components/rfxtrx/config_flow.py @@ -34,7 +34,13 @@ from homeassistant.helpers.entity_registry import ( 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 @@ CONF_MANUAL_PATH = "Enter Manually" class DeviceData(TypedDict): """Dict data representing a device entry.""" - event_code: str + event_code: str | None device_id: DeviceTuple @@ -388,15 +394,15 @@ class OptionsFlow(config_entries.OptionsFlow): 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 9c0301d97e6..de0c3d393ca 100644 --- a/homeassistant/components/group/binary_sensor.py +++ b/homeassistant/components/group/binary_sensor.py @@ -17,6 +17,7 @@ from homeassistant.const import ( 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 @@ class BinarySensorGroup(GroupEntity, BinarySensorEntity): 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 @@ class BinarySensorGroup(GroupEntity, BinarySensorEntity): 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 0a85c793aaa..a0872b11f16 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 44de321db4a..dfd45991b3f 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 4d6dcefb10f..91afd8a8e55 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 c7828110fc2..0d228b65601 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 5fcb5027640..b994bc80f4a 100644 --- a/homeassistant/components/homekit_controller/number.py +++ b/homeassistant/components/homekit_controller/number.py @@ -48,42 +48,6 @@ NUMBER_ENTITIES: dict[str, NumberEntityDescription] = { 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 83378650b97..3c47195b442 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 @@ from homeassistant.components.climate.const import ( 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 1166d1db6db..d7ee08f85f8 100644 --- a/homeassistant/components/dlna_dms/dms.py +++ b/homeassistant/components/dlna_dms/dms.py @@ -542,7 +542,7 @@ class DmsDeviceSource: 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 @@ class DmsDeviceSource: """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 4ee9cce91ba..d6fcdb267d6 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 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.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 1e9f34608eb..1d4d47748be 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 91afd8a8e55..9714786a873 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 0d228b65601..8f539520713 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 1a9f3dc0314..3281410ec2d 100644 --- a/homeassistant/components/flipr/__init__.py +++ b/homeassistant/components/flipr/__init__.py @@ -75,8 +75,7 @@ class FliprDataUpdateCoordinator(DataUpdateCoordinator): 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 48f9361723c..fc24ddee340 100644 --- a/tests/components/flipr/test_binary_sensor.py +++ b/tests/components/flipr/test_binary_sensor.py @@ -5,6 +5,7 @@ from unittest.mock import patch 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 45816801472..c5ab3dc1541 100644 --- a/tests/components/flipr/test_sensor.py +++ b/tests/components/flipr/test_sensor.py @@ -13,6 +13,7 @@ from homeassistant.const import ( 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 8240691b247..6ba1b7b2b9a 100644 --- a/homeassistant/components/radio_browser/media_source.py +++ b/homeassistant/components/radio_browser/media_source.py @@ -12,6 +12,7 @@ from homeassistant.components.media_player.const import ( 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 @@ class RadioMediaSource(MediaSource): 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 @@ class RadioMediaSource(MediaSource): 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 @@ class RadioMediaSource(MediaSource): 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 @@ class RadioMediaSource(MediaSource): 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 @@ class RadioMediaSource(MediaSource): 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 @@ class RadioMediaSource(MediaSource): 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 @@ class RadioMediaSource(MediaSource): 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 51af5c06d22..c0ff111fc89 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from .backports.enum import StrEnum 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 4d686e63c5b..d72829b574e 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 2dcfe639a64..8dda4d32644 100644 --- a/homeassistant/components/rfxtrx/__init__.py +++ b/homeassistant/components/rfxtrx/__init__.py @@ -6,7 +6,7 @@ import binascii 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 @@ async def async_setup_internal(hass, entry: config_entries.ConfigEntry): 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 @@ async def async_setup_internal(hass, entry: config_entries.ConfigEntry): 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 ce545684da5..a62ef8fb2d7 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 a62ef8fb2d7..26811dae73e 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 | 104 ++++++++++++++++++---------------- 1 file changed, 54 insertions(+), 50 deletions(-) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index 26811dae73e..a857ef77edb 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} + local tag_l=${1} + local tag_r=${2} - 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 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 "${docker_reg}/home-assistant:${tag_l}" \ - "${docker_reg}/amd64-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 }}" + 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 "${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 }}" + 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 "${docker_reg}" "${{ needs.init.outputs.version }}" "${{ 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 "${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 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 "${docker_reg}" "${v%.*}" "${{ needs.init.outputs.version }}" - fi - done + # 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 a857ef77edb..2ca9b754a3a 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"